From 3dc26cd4aec0112ce73c0ad36a30fde78017252f Mon Sep 17 00:00:00 2001 From: Alex Humesky Date: Tue, 11 Jul 2023 18:40:41 -0400 Subject: [PATCH 1/6] Revert "Remove desugar binary from Bazel Android Tools" This reverts commit 6d05f257358341b910ba1075596a180a767bcfa2. Re-add "classic" desugar dependencies in the Bazel 6.3.0 branch so that a version of the Android tools can be built that includes the classic desugarer so that we don't introduce breaking changes in a minor release of Bazel. --- .../android/java/com/google/devtools/build/android/BUILD | 4 +--- .../google/devtools/build/android/desugar/dependencies/BUILD | 1 - .../java/com/google/devtools/build/android/desugar/io/BUILD | 1 - .../com/google/devtools/build/android/desugar/langmodel/BUILD | 1 - 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD b/src/tools/android/java/com/google/devtools/build/android/BUILD index a9925e13623a6d..be017fb8f23a42 100644 --- a/src/tools/android/java/com/google/devtools/build/android/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/BUILD @@ -26,9 +26,7 @@ java_binary( runtime_deps = [ ":android_builder_lib", "//src/main/java/com/google/devtools/build/lib/worker:work_request_handlers", - "//src/tools/android/java/com/google/devtools/build/android/desugar/dependencies", - "//src/tools/android/java/com/google/devtools/build/android/desugar/io", - "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//src/tools/android/java/com/google/devtools/build/android/desugar", "//src/tools/android/java/com/google/devtools/build/android/desugar/scan", "//src/tools/android/java/com/google/devtools/build/android/dexer:dexerdeps", "//src/tools/android/java/com/google/devtools/build/android/idlclass:idlclass_lib", diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/BUILD index d590e03212134a..d1564f02bc6f9f 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/BUILD @@ -7,7 +7,6 @@ load("@rules_java//java:defs.bzl", "java_library") package( default_visibility = [ "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", - "//src/tools/android/java/com/google/devtools/build/android:__pkg__", "//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__", "//src/tools/android/java/com/google/devtools/build/android/r8:__pkg__", ], diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/io/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/io/BUILD index 0afbef530e6114..e97e1a5c4d3148 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/io/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/io/BUILD @@ -8,7 +8,6 @@ java_library( srcs = glob(["*.java"]), visibility = [ "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", - "//src/tools/android/java/com/google/devtools/build/android:__pkg__", "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", ], deps = [ diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/BUILD index de28ae22988216..137dcd5ac3243c 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/BUILD @@ -3,7 +3,6 @@ load("@rules_java//java:defs.bzl", "java_library") package( default_visibility = [ "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", - "//src/tools/android/java/com/google/devtools/build/android:__pkg__", "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", ], ) From 7822d7e947393673796da5691c5e76c996ab3b6a Mon Sep 17 00:00:00 2001 From: Alex Humesky Date: Tue, 11 Jul 2023 19:13:08 -0400 Subject: [PATCH 2/6] Revert "Clean Classic Desugar Source Code - Production - 2 / 2" This reverts commit 8ec242813449ee0468120e4fc164a8b20cce75c3. --- .../devtools/build/android/desugar/BUILD | 10 + .../desugar/BytecodeTypeInference.java | 1013 ++++++++++++ .../android/desugar/ClassReaderFactory.java | 64 + .../android/desugar/ClassSignatureParser.java | 269 ++++ .../android/desugar/ClassVsInterface.java | 70 + .../desugar/CloseResourceMethodScanner.java | 116 ++ .../CoreLibraryInvocationRewriter.java | 104 ++ .../android/desugar/CoreLibrarySupport.java | 600 +++++++ .../android/desugar/CorePackageRenamer.java | 123 ++ .../desugar/DefaultMethodClassFixer.java | 1001 ++++++++++++ .../build/android/desugar/Desugar.java | 1162 ++++++++++++++ .../desugar/EmulatedInterfaceRewriter.java | 132 ++ .../android/desugar/GeneratedClassStore.java | 47 + .../android/desugar/InterfaceDesugaring.java | 694 ++++++++ .../InvokeDynamicLambdaMethodCollector.java | 82 + .../android/desugar/Java7Compatibility.java | 300 ++++ .../android/desugar/LambdaClassFixer.java | 536 +++++++ .../android/desugar/LambdaClassMaker.java | 102 ++ .../android/desugar/LambdaDesugaring.java | 727 +++++++++ .../build/android/desugar/LambdaInfo.java | 60 + .../desugar/LongCompareMethodRewriter.java | 62 + .../build/android/desugar/MethodInfo.java | 31 + .../ObjectsRequireNonNullMethodRewriter.java | 70 + .../desugar/TryWithResourcesRewriter.java | 464 ++++++ .../build/android/desugar/config/BUILD | 23 + .../desugar/config/DesugarOptions.java | 310 ++++ .../android/desugar/corelibadapter/BUILD | 32 + .../InvocationSiteTransformationReason.java | 51 + .../InvocationSiteTransformationRecord.java | 57 + .../ShadowedApiAdapterHelper.java | 226 +++ .../ShadowedApiAdaptersGenerator.java | 187 +++ .../ShadowedApiInvocationSite.java | 306 ++++ .../android/desugar/covariantreturn/BUILD | 28 + .../NioBufferRefConverter.java | 133 ++ .../devtools/build/android/desugar/desugar.sh | 35 + .../devtools/build/android/desugar/nest/BUILD | 29 + .../nest/FieldAccessBridgeEmitter.java | 134 ++ .../desugar/nest/MethodAccessorEmitter.java | 228 +++ .../android/desugar/nest/NestAnalyzer.java | 36 + .../desugar/nest/NestBridgeRefConverter.java | 249 +++ .../desugar/nest/NestDesugarConstants.java | 25 + .../android/desugar/nest/NestDesugaring.java | 174 ++ .../android/desugar/nest/NestDigest.java | 216 +++ .../build/android/desugar/preanalysis/BUILD | 32 + .../preanalysis/ClassMetadataCollector.java | 211 +++ .../desugar/preanalysis/InputPreAnalyzer.java | 74 + .../build/android/desugar/retarget/BUILD | 48 + .../retarget/ClassMemberRetargetConfig.java | 147 ++ .../retarget/ClassMemberRetargetRewriter.java | 137 ++ .../android/desugar/retarget/retarget.proto | 43 + .../retarget/retarget_config.textproto | 1419 +++++++++++++++++ .../build/android/desugar/runtime/BUILD | 49 + .../desugar/runtime/PrimitiveHashcode.java | 56 + .../desugar/runtime/StringConcats.java | 39 + .../desugar/runtime/ThrowableExtension.java | 412 +++++ .../android/desugar/runtime/UnsignedInts.java | 102 ++ .../desugar/runtime/UnsignedLongs.java | 178 +++ .../build/android/desugar/strconcat/BUILD | 28 + .../strconcat/IndyStringConcatDesugaring.java | 171 ++ .../typeadapter/java/time/ClockConverter.java | 66 + .../java/time/DurationConverter.java | 36 + .../java/time/InstantConverter.java | 36 + .../java/time/LocalDateConverter.java | 38 + .../java/time/LocalTimeConverter.java | 32 + .../java/time/MonthDayConverter.java | 36 + .../java/time/PeriodConverter.java | 36 + .../java/time/ZoneIdConverter.java | 32 + .../java/time/ZonedDateTimeConverter.java | 54 + .../util/function/BiConsumerConverter.java | 57 + .../util/function/BiFunctionConverter.java | 57 + .../function/BinaryOperatorConverter.java | 57 + .../java/util/function/ConsumerConverter.java | 55 + .../DoubleUnaryOperatorConverter.java | 64 + .../java/util/function/FunctionConverter.java | 63 + .../util/function/IntConsumerConverter.java | 55 + .../util/function/IntFunctionConverter.java | 51 + .../util/function/PredicateConverter.java | 66 + .../util/function/UnaryOperatorConverter.java | 63 + .../android/desugar/typeannotation/BUILD | 28 + .../LocalTypeAnnotationUse.java | 73 + .../build/android/desugar/typehierarchy/BUILD | 30 + .../typehierarchy/HeadlessMethodKey.java | 40 + .../typehierarchy/HierarchicalMethodKey.java | 48 + .../HierarchicalMethodQuery.java | 107 ++ .../typehierarchy/HierarchicalTypeKey.java | 38 + .../typehierarchy/HierarchicalTypeQuery.java | 120 ++ .../desugar/typehierarchy/TypeHierarchy.java | 139 ++ .../TypeHierarchyClassVisitor.java | 97 ++ .../typehierarchy/TypeHierarchyScavenger.java | 72 + 89 files changed, 15210 insertions(+) create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/ClassSignatureParser.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/ClassVsInterface.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/CorePackageRenamer.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/GeneratedClassStore.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/InvokeDynamicLambdaMethodCollector.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/Java7Compatibility.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/LambdaInfo.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/LongCompareMethodRewriter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/MethodInfo.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodRewriter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/config/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/config/DesugarOptions.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/InvocationSiteTransformationReason.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/InvocationSiteTransformationRecord.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverter.java create mode 100755 src/tools/android/java/com/google/devtools/build/android/desugar/desugar.sh create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/nest/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/nest/FieldAccessBridgeEmitter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/nest/MethodAccessorEmitter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestAnalyzer.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestBridgeRefConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDesugarConstants.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDesugaring.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDigest.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/ClassMetadataCollector.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/InputPreAnalyzer.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/retarget/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/retarget/ClassMemberRetargetConfig.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/retarget/ClassMemberRetargetRewriter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget.proto create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget_config.textproto create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/runtime/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/runtime/StringConcats.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtension.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedInts.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongs.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ClockConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/DurationConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/InstantConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalDateConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalTimeConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/MonthDayConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/PeriodConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ZoneIdConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ZonedDateTimeConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiConsumerConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiFunctionConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BinaryOperatorConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/ConsumerConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/DoubleUnaryOperatorConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/FunctionConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntConsumerConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntFunctionConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/PredicateConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/UnaryOperatorConverter.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation/LocalTypeAnnotationUse.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HeadlessMethodKey.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalMethodKey.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalMethodQuery.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalTypeKey.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalTypeQuery.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchy.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyClassVisitor.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyScavenger.java diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD index 19ca2a60c98233..53c5258cca3a09 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD @@ -29,6 +29,16 @@ filegroup( "//src/tools/android/java/com/google/devtools/build/android/desugar/dependencies:srcs", "//src/tools/android/java/com/google/devtools/build/android/desugar/io:srcs", "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/nest:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/retarget:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/config:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/strconcat:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis:srcs", "//src/tools/android/java/com/google/devtools/build/android/desugar/scan:srcs", ], visibility = ["//src/tools/android/java/com/google/devtools/build/android:__pkg__"], diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java b/src/tools/android/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java new file mode 100644 index 00000000000000..2124fe092bcfbe --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/BytecodeTypeInference.java @@ -0,0 +1,1013 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.android.desugar; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.devtools.build.android.desugar.io.BitFlags.isStatic; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import org.objectweb.asm.Opcodes; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.CheckReturnValue; +import java.util.ArrayList; +import java.util.Optional; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * Perform type inference for byte code (local variables and operand stack) with the help of stack + * map frames. + * + *

Note: This class only guarantees the correctness of reference types, but not the primitive + * types, though they might be correct too. + */ +@CheckReturnValue // a good practice in general, and preparation for copying this into Truth +final class BytecodeTypeInference extends MethodVisitor { + + private boolean used = false; + private final ArrayList localVariableSlots; + private final ArrayList operandStack = new ArrayList<>(); + private FrameInfo previousFrame; + /** For debugging purpose. */ + private final String methodSignature; + + BytecodeTypeInference(int access, String owner, String name, String methodDescriptor) { + super(Opcodes.ASM9); + localVariableSlots = createInitialLocalVariableTypes(access, owner, name, methodDescriptor); + previousFrame = FrameInfo.create(ImmutableList.copyOf(localVariableSlots), ImmutableList.of()); + this.methodSignature = owner + "." + name + methodDescriptor; + } + + void setDelegateMethodVisitor(MethodVisitor visitor) { + mv = visitor; + } + + @Override + public void visitCode() { + checkState(!used, "Cannot reuse this method visitor."); + used = true; + super.visitCode(); + } + + /** Returns the type of a value in the operand. 0 means the top of the stack. */ + InferredType getTypeOfOperandFromTop(int offsetFromTop) { + int index = operandStack.size() - 1 - offsetFromTop; + checkState( + index >= 0, + "Invalid offset %s in the list of size %s. The current method is %s", + offsetFromTop, + operandStack.size(), + methodSignature); + return operandStack.get(index); + } + + String getOperandStackAsString() { + return operandStack.toString(); + } + + String getLocalsAsString() { + return localVariableSlots.toString(); + } + + @Override + public void visitInsn(int opcode) { + switch (opcode) { + case Opcodes.NOP: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.RETURN: + break; + case Opcodes.ACONST_NULL: + push(InferredType.NULL); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + push(InferredType.INT); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + push(InferredType.LONG); + push(InferredType.TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + push(InferredType.FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + push(InferredType.DOUBLE); + push(InferredType.TOP); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + pop(2); + push(InferredType.INT); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(InferredType.LONG); + push(InferredType.TOP); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(InferredType.DOUBLE); + push(InferredType.TOP); + break; + case Opcodes.AALOAD: + InferredType arrayType = pop(2); + InferredType elementType = arrayType.getElementTypeIfArrayOrThrow(); + push(elementType); + break; + case Opcodes.IASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.FASTORE: + case Opcodes.AASTORE: + pop(3); + break; + case Opcodes.LASTORE: + case Opcodes.DASTORE: + pop(4); + break; + case Opcodes.POP: + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + pop(); + break; + case Opcodes.POP2: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + pop(2); + break; + case Opcodes.DUP: + push(top()); + break; + case Opcodes.DUP_X1: + { + InferredType top = pop(); + InferredType next = pop(); + push(top); + push(next); + push(top); + break; + } + case Opcodes.DUP_X2: + { + InferredType top = pop(); + InferredType next = pop(); + InferredType bottom = pop(); + push(top); + push(bottom); + push(next); + push(top); + break; + } + case Opcodes.DUP2: + { + InferredType top = pop(); + InferredType next = pop(); + push(next); + push(top); + push(next); + push(top); + break; + } + case Opcodes.DUP2_X1: + { + InferredType top = pop(); + InferredType next = pop(); + InferredType bottom = pop(); + push(next); + push(top); + push(bottom); + push(next); + push(top); + break; + } + case Opcodes.DUP2_X2: + { + InferredType t1 = pop(); + InferredType t2 = pop(); + InferredType t3 = pop(); + InferredType t4 = pop(); + push(t2); + push(t1); + push(t4); + push(t3); + push(t2); + push(t1); + break; + } + case Opcodes.SWAP: + { + InferredType top = pop(); + InferredType next = pop(); + push(top); + push(next); + break; + } + case Opcodes.IADD: + case Opcodes.ISUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.IREM: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.L2I: + case Opcodes.D2I: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + pop(2); + push(InferredType.INT); + break; + + case Opcodes.LADD: + case Opcodes.LSUB: + case Opcodes.LMUL: + case Opcodes.LDIV: + case Opcodes.LREM: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + pop(4); + push(InferredType.LONG); + push(InferredType.TOP); + break; + + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(InferredType.LONG); + push(InferredType.TOP); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(); + push(InferredType.LONG); + push(InferredType.TOP); + break; + case Opcodes.I2F: + pop(); + push(InferredType.FLOAT); + break; + + case Opcodes.LCMP: + case Opcodes.DCMPG: + case Opcodes.DCMPL: + pop(4); + push(InferredType.INT); + break; + + case Opcodes.I2D: + case Opcodes.F2D: + pop(); + push(InferredType.DOUBLE); + push(InferredType.TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + pop(); + push(InferredType.INT); + break; + case Opcodes.FALOAD: + case Opcodes.FADD: + case Opcodes.FSUB: + case Opcodes.FMUL: + case Opcodes.FDIV: + case Opcodes.FREM: + case Opcodes.L2F: + case Opcodes.D2F: + pop(2); + push(InferredType.FLOAT); + break; + + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(InferredType.DOUBLE); + push(InferredType.TOP); + break; + default: + throw new RuntimeException("Unhandled opcode " + opcode); + } + super.visitInsn(opcode); + } + + @Override + public void visitIntInsn(int opcode, int operand) { + switch (opcode) { + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + push(InferredType.INT); + break; + case Opcodes.NEWARRAY: + pop(); + switch (operand) { + case Opcodes.T_BOOLEAN: + pushDescriptor("[Z"); + break; + case Opcodes.T_CHAR: + pushDescriptor("[C"); + break; + case Opcodes.T_FLOAT: + pushDescriptor("[F"); + break; + case Opcodes.T_DOUBLE: + pushDescriptor("[D"); + break; + case Opcodes.T_BYTE: + pushDescriptor("[B"); + break; + case Opcodes.T_SHORT: + pushDescriptor("[S"); + break; + case Opcodes.T_INT: + pushDescriptor("[I"); + break; + case Opcodes.T_LONG: + pushDescriptor("[J"); + break; + default: + throw new RuntimeException("Unhandled operand value: " + operand); + } + break; + default: + throw new RuntimeException("Unhandled opcode " + opcode); + } + super.visitIntInsn(opcode, operand); + } + + @Override + public void visitVarInsn(int opcode, int var) { + switch (opcode) { + case Opcodes.ILOAD: + push(InferredType.INT); + break; + case Opcodes.LLOAD: + push(InferredType.LONG); + push(InferredType.TOP); + break; + case Opcodes.FLOAD: + push(InferredType.FLOAT); + break; + case Opcodes.DLOAD: + push(InferredType.DOUBLE); + push(InferredType.TOP); + break; + case Opcodes.ALOAD: + push(getLocalVariableType(var)); + break; + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + { + InferredType type = pop(); + setLocalVariableTypes(var, type); + break; + } + case Opcodes.LSTORE: + case Opcodes.DSTORE: + { + InferredType type = pop(2); + setLocalVariableTypes(var, type); + setLocalVariableTypes(var + 1, InferredType.TOP); + break; + } + case Opcodes.RET: + throw new RuntimeException("The instruction RET is not supported"); + default: + throw new RuntimeException("Unhandled opcode " + opcode); + } + super.visitVarInsn(opcode, var); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + String descriptor = convertToDescriptor(type); + switch (opcode) { + case Opcodes.NEW: + // This should be UNINITIALIZED(label). Okay for type inference. + pushDescriptor(descriptor); + break; + case Opcodes.ANEWARRAY: + pop(); + pushDescriptor('[' + descriptor); + break; + case Opcodes.CHECKCAST: + pop(); + pushDescriptor(descriptor); + break; + case Opcodes.INSTANCEOF: + pop(); + push(InferredType.INT); + break; + default: + throw new RuntimeException("Unhandled opcode " + opcode); + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + switch (opcode) { + case Opcodes.GETSTATIC: + pushDescriptor(desc); + break; + case Opcodes.PUTSTATIC: + popDescriptor(desc); + break; + case Opcodes.GETFIELD: + pop(); + pushDescriptor(desc); + break; + case Opcodes.PUTFIELD: + popDescriptor(desc); + pop(); + break; + default: + throw new RuntimeException( + "Unhandled opcode " + opcode + ", owner=" + owner + ", name=" + name + ", desc" + desc); + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == Opcodes.INVOKESPECIAL && "".equals(name)) { + int argumentSize = (Type.getArgumentsAndReturnSizes(desc) >> 2); + InferredType receiverType = getTypeOfOperandFromTop(argumentSize - 1); + if (receiverType.isUninitialized()) { + InferredType realType = InferredType.create('L' + owner + ';'); + replaceUninitializedTypeInStack(receiverType, realType); + } + } + switch (opcode) { + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + popDescriptor(desc); + if (opcode != Opcodes.INVOKESTATIC) { + pop(); // Pop receiver. + } + pushDescriptor(desc); + break; + default: + throw new RuntimeException( + String.format( + "Unhandled opcode %s, owner=%s, name=%s, desc=%s, itf=%s", + opcode, owner, name, desc, itf)); + } + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + popDescriptor(desc); + pushDescriptor(desc); + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + switch (opcode) { + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + pop(); + break; + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + pop(2); + break; + case Opcodes.GOTO: + break; + case Opcodes.JSR: + throw new RuntimeException("The JSR instruction is not supported."); + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + pop(1); + break; + default: + throw new RuntimeException("Unhandled opcode " + opcode); + } + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitLdcInsn(Object cst) { + if (cst instanceof Integer) { + push(InferredType.INT); + } else if (cst instanceof Float) { + push(InferredType.FLOAT); + } else if (cst instanceof Long) { + push(InferredType.LONG); + push(InferredType.TOP); + } else if (cst instanceof Double) { + push(InferredType.DOUBLE); + push(InferredType.TOP); + } else if (cst instanceof String) { + pushDescriptor("Ljava/lang/String;"); + } else if (cst instanceof Type) { + pushDescriptor(((Type) cst).getDescriptor()); + } else if (cst instanceof Handle) { + pushDescriptor("Ljava/lang/invoke/MethodHandle;"); + } else { + throw new RuntimeException("Cannot handle constant " + cst + " for LDC instruction"); + } + super.visitLdcInsn(cst); + } + + @Override + public void visitIincInsn(int var, int increment) { + setLocalVariableTypes(var, InferredType.INT); + super.visitIincInsn(var, increment); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + pop(); + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + pop(); + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + pop(dims); + pushDescriptor(desc); + super.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + switch (type) { + case Opcodes.F_NEW: + // Expanded form. + previousFrame = + FrameInfo.create( + convertTypesInStackMapFrame(nLocal, local), + convertTypesInStackMapFrame(nStack, stack)); + break; + case Opcodes.F_SAME: + // This frame type indicates that the frame has exactly the same local variables as the + // previous frame and that the operand stack is empty. + previousFrame = FrameInfo.create(previousFrame.locals(), ImmutableList.of()); + break; + case Opcodes.F_SAME1: + // This frame type indicates that the frame has exactly the same local variables as the + // previous frame and that the operand stack has one entry. + previousFrame = + FrameInfo.create(previousFrame.locals(), convertTypesInStackMapFrame(nStack, stack)); + break; + case Opcodes.F_APPEND: + // This frame type indicates that the frame has the same locals as the previous frame except + // that k additional locals are defined, and that the operand stack is empty. + previousFrame = + FrameInfo.create( + appendArrayToList(previousFrame.locals(), nLocal, local), ImmutableList.of()); + break; + case Opcodes.F_CHOP: + // This frame type indicates that the frame has the same local variables as the previous + // frame except that the last k local variables are absent, and that the operand stack is + // empty. + previousFrame = + FrameInfo.create( + removeBackFromList(previousFrame.locals(), nLocal), ImmutableList.of()); + break; + case Opcodes.F_FULL: + previousFrame = + FrameInfo.create( + convertTypesInStackMapFrame(nLocal, local), + convertTypesInStackMapFrame(nStack, stack)); + break; + default: + // continue below + } + // Update types for operand stack and local variables. + operandStack.clear(); + operandStack.addAll(previousFrame.stack()); + localVariableSlots.clear(); + localVariableSlots.addAll(previousFrame.locals()); + super.visitFrame(type, nLocal, local, nStack, stack); + } + + private static String convertToDescriptor(String type) { + return (type.length() > 1 && type.charAt(0) != '[') ? 'L' + type + ';' : type; + } + + private void push(InferredType type) { + operandStack.add(type); + } + + private void replaceUninitializedTypeInStack(InferredType oldType, InferredType newType) { + checkArgument(oldType.isUninitialized(), "The old type is NOT uninitialized. %s", oldType); + for (int i = 0, size = operandStack.size(); i < size; ++i) { + InferredType type = operandStack.get(i); + if (type.equals(oldType)) { + operandStack.set(i, newType); + } + } + } + + private void pushDescriptor(String desc) { + int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0; + switch (desc.charAt(index)) { + case 'V': + return; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + push(InferredType.INT); + break; + case 'F': + push(InferredType.FLOAT); + break; + case 'D': + push(InferredType.DOUBLE); + push(InferredType.TOP); + break; + case 'J': + push(InferredType.LONG); + push(InferredType.TOP); + break; + case 'L': + case '[': + push(InferredType.create(desc.substring(index))); + break; + default: + throw new RuntimeException("Unhandled type: " + desc); + } + } + + @CanIgnoreReturnValue + private InferredType pop() { + return pop(1); + } + + /** Pop elements from the end of the operand stack, and return the last popped element. */ + @CanIgnoreReturnValue + private InferredType pop(int count) { + checkArgument( + count >= 1, "The count should be at least one: %s (In %s)", count, methodSignature); + checkState( + operandStack.size() >= count, + "There are no enough elements in the stack. count=%s, stack=%s (In %s)", + count, + operandStack, + methodSignature); + int expectedLastIndex = operandStack.size() - count - 1; + InferredType lastPopped = null; + for (int i = operandStack.size() - 1; i > expectedLastIndex; --i) { + lastPopped = operandStack.remove(i); + } + return lastPopped; + } + + private void popDescriptor(String desc) { + char c = desc.charAt(0); + switch (c) { + case '(': + int argumentSize = (Type.getArgumentsAndReturnSizes(desc) >> 2) - 1; + if (argumentSize > 0) { + pop(argumentSize); + } + break; + case 'J': + case 'D': + pop(2); + break; + default: + pop(1); + break; + } + } + + private InferredType getLocalVariableType(int index) { + checkState( + index < localVariableSlots.size(), + "Cannot find type for var %s in method %s", + index, + methodSignature); + return localVariableSlots.get(index); + } + + private void setLocalVariableTypes(int index, InferredType type) { + while (localVariableSlots.size() <= index) { + localVariableSlots.add(InferredType.TOP); + } + localVariableSlots.set(index, type); + } + + private InferredType top() { + return operandStack.get(operandStack.size() - 1); + } + + /** + * Create the types of local variables at the very beginning of the method with the information of + * the declaring class and the method descriptor. + */ + private static ArrayList createInitialLocalVariableTypes( + int access, String ownerClass, String methodName, String methodDescriptor) { + ArrayList types = new ArrayList<>(); + + if (!isStatic(access)) { + // Instance method, and this is the receiver + types.add(InferredType.create(convertToDescriptor(ownerClass))); + } + Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor); + for (Type argumentType : argumentTypes) { + switch (argumentType.getSort()) { + case Type.BOOLEAN: + case Type.BYTE: + case Type.CHAR: + case Type.SHORT: + case Type.INT: + types.add(InferredType.INT); + break; + case Type.FLOAT: + types.add(InferredType.FLOAT); + break; + case Type.LONG: + types.add(InferredType.LONG); + types.add(InferredType.TOP); + break; + case Type.DOUBLE: + types.add(InferredType.DOUBLE); + types.add(InferredType.TOP); + break; + case Type.ARRAY: + case Type.OBJECT: + types.add(InferredType.create(argumentType.getDescriptor())); + break; + default: + throw new RuntimeException( + "Unhandled argument type: " + + argumentType + + " in " + + ownerClass + + "." + + methodName + + methodDescriptor); + } + } + return types; + } + + private static ImmutableList removeBackFromList( + ImmutableList list, int countToRemove) { + int origSize = list.size(); + int index = origSize - 1; + + while (index >= 0 && countToRemove > 0) { + InferredType type = list.get(index); + if (type.equals(InferredType.TOP) && index > 0 && list.get(index - 1).isCategory2()) { + --index; // A category 2 takes two slots. + } + --index; // Eat this local variable. + --countToRemove; + } + checkState( + countToRemove == 0, + "countToRemove is %s but not 0. index=%s, list=%s", + countToRemove, + index, + list); + return list.subList(0, index + 1); + } + + private ImmutableList appendArrayToList( + ImmutableList list, int size, Object[] array) { + ImmutableList.Builder builder = ImmutableList.builder(); + builder.addAll(list); + for (int i = 0; i < size; ++i) { + InferredType type = convertTypeInStackMapFrame(array[i]); + builder.add(type); + if (type.isCategory2()) { + builder.add(InferredType.TOP); + } + } + return builder.build(); + } + + /** Convert the type in stack map frame to inference type. */ + private InferredType convertTypeInStackMapFrame(Object typeInStackMapFrame) { + if (typeInStackMapFrame == Opcodes.TOP) { + return InferredType.TOP; + } else if (typeInStackMapFrame == Opcodes.INTEGER) { + return InferredType.INT; + } else if (typeInStackMapFrame == Opcodes.FLOAT) { + return InferredType.FLOAT; + } else if (typeInStackMapFrame == Opcodes.DOUBLE) { + return InferredType.DOUBLE; + } else if (typeInStackMapFrame == Opcodes.LONG) { + return InferredType.LONG; + } else if (typeInStackMapFrame == Opcodes.NULL) { + return InferredType.NULL; + } else if (typeInStackMapFrame == Opcodes.UNINITIALIZED_THIS) { + return InferredType.UNINITIALIZED_THIS; + } else if (typeInStackMapFrame instanceof String) { + String referenceTypeName = (String) typeInStackMapFrame; + if (referenceTypeName.charAt(0) == '[') { + return InferredType.create(referenceTypeName); + } else { + return InferredType.create('L' + referenceTypeName + ';'); + } + } else if (typeInStackMapFrame instanceof Label) { + return InferredType.UNINITIALIZED; + } else { + throw new RuntimeException( + "Cannot reach here. Unhandled element: value=" + + typeInStackMapFrame + + ", class=" + + typeInStackMapFrame.getClass() + + ". The current method being desugared is " + + methodSignature); + } + } + + private ImmutableList convertTypesInStackMapFrame(int size, Object[] array) { + ImmutableList.Builder 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 locals, ImmutableList stack) { + return new AutoValue_BytecodeTypeInference_FrameInfo(locals, stack); + } + + abstract ImmutableList locals(); + + abstract ImmutableList stack(); + } + + /** This is the type used for type inference. */ + @AutoValue + abstract static class InferredType { + + static final String UNINITIALIZED_PREFIX = "UNINIT@"; + + static final InferredType BOOLEAN = new AutoValue_BytecodeTypeInference_InferredType("Z"); + static final InferredType BYTE = new AutoValue_BytecodeTypeInference_InferredType("B"); + static final InferredType INT = new AutoValue_BytecodeTypeInference_InferredType("I"); + static final InferredType FLOAT = new AutoValue_BytecodeTypeInference_InferredType("F"); + static final InferredType LONG = new AutoValue_BytecodeTypeInference_InferredType("J"); + static final InferredType DOUBLE = new AutoValue_BytecodeTypeInference_InferredType("D"); + /** Not a real value. */ + static final InferredType TOP = new AutoValue_BytecodeTypeInference_InferredType("TOP"); + /** The value NULL */ + static final InferredType NULL = new AutoValue_BytecodeTypeInference_InferredType("NULL"); + + static final InferredType UNINITIALIZED_THIS = + new AutoValue_BytecodeTypeInference_InferredType("UNINITIALIZED_THIS"); + + static final InferredType UNINITIALIZED = + new AutoValue_BytecodeTypeInference_InferredType(UNINITIALIZED_PREFIX); + + /** Create a type for a value. */ + static InferredType create(String descriptor) { + if (UNINITIALIZED_PREFIX.equals(descriptor)) { + return UNINITIALIZED; + } + char firstChar = descriptor.charAt(0); + if (firstChar == 'L' || firstChar == '[') { + // Reference, array. + return new AutoValue_BytecodeTypeInference_InferredType(descriptor); + } + switch (descriptor) { + case "Z": + return BOOLEAN; + case "B": + return BYTE; + case "I": + return INT; + case "F": + return FLOAT; + case "J": + return LONG; + case "D": + return DOUBLE; + case "TOP": + return TOP; + case "NULL": + return NULL; + case "UNINITIALIZED_THIS": + return UNINITIALIZED_THIS; + default: + throw new RuntimeException("Invalid descriptor: " + descriptor); + } + } + + abstract String descriptor(); + + @Override + public String toString() { + return descriptor(); + } + + /** Is a category 2 value? */ + boolean isCategory2() { + String descriptor = descriptor(); + return descriptor.equals("J") || descriptor.equals("D"); + } + + /** If the type is an array, return the element type. Otherwise, throw an exception. */ + InferredType getElementTypeIfArrayOrThrow() { + String descriptor = descriptor(); + checkState(descriptor.charAt(0) == '[', "This type %s is not an array.", this); + return create(descriptor.substring(1)); + } + + /** Is an uninitialized value? */ + boolean isUninitialized() { + return descriptor().startsWith(UNINITIALIZED_PREFIX); + } + + /** Is a null value? */ + boolean isNull() { + return NULL.equals(this); + } + + /** + * If this type is a reference type, then return the internal name. Otherwise, returns empty. + */ + Optional getInternalName() { + String descriptor = descriptor(); + int length = descriptor.length(); + if (length > 0 && descriptor.charAt(0) == 'L' && descriptor.charAt(length - 1) == ';') { + return Optional.of(descriptor.substring(1, length - 1)); + } else { + return Optional.empty(); + } + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java new file mode 100644 index 00000000000000..338051abd52883 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java @@ -0,0 +1,64 @@ +// 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 com.google.devtools.build.android.desugar.io.CoreLibraryRewriter; +import com.google.devtools.build.android.desugar.io.IndexedInputs; +import com.google.devtools.build.android.desugar.io.InputFileProvider; +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nullable; +import org.objectweb.asm.ClassReader; + +class ClassReaderFactory { + private final IndexedInputs indexedInputs; + private final CoreLibraryRewriter rewriter; + + public ClassReaderFactory(IndexedInputs indexedInputs, CoreLibraryRewriter rewriter) { + this.rewriter = rewriter; + this.indexedInputs = indexedInputs; + } + + /** + * Returns a reader for the given/internal/Class$Name if the class is defined in the wrapped input + * and {@code null} otherwise. For simplicity this method turns checked into runtime exceptions + * under the assumption that all classes have already been read once when this method is called. + */ + @Nullable + public ClassReader readIfKnown(String internalClassName) { + String filename = rewriter.unprefix(internalClassName) + ".class"; + InputFileProvider inputFileProvider = indexedInputs.getInputFileProvider(filename); + + if (inputFileProvider != null) { + try (InputStream bytecode = inputFileProvider.getInputStream(filename)) { + // ClassReader doesn't take ownership and instead eagerly reads the stream's contents + return rewriter.reader(bytecode); + } catch (IOException e) { + // We should've already read through all files in the Jar once at this point, so we don't + // expect failures reading some files a second time. + throw new IllegalStateException("Couldn't load " + internalClassName, e); + } + } + + return null; + } + + /** + * Returns {@code true} if the given given/internal/Class$Name is defined in the wrapped input. + */ + public boolean isKnown(String internalClassName) { + String filename = rewriter.unprefix(internalClassName) + ".class"; + return indexedInputs.getInputFileProvider(filename) != null; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/ClassSignatureParser.java b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassSignatureParser.java new file mode 100644 index 00000000000000..06cae2e21f088c --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassSignatureParser.java @@ -0,0 +1,269 @@ +// Copyright 2019 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.checkState; +import static com.google.common.base.Verify.verify; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import java.util.Arrays; + +/** + * Parses the class signature as specified by $4.7.9.1 + * of the Java Virtual Machine Specification. + */ +public final class ClassSignatureParser { + + private final String signature; + private final String[] interfaceTypeParameters; + private int index; + private int numInterfaces; + private String generics; + private SuperclassSignature superclassSignature; + + /** + * Rudimentary parser for class signatures as specified by $4.7.9.1. + * It returns an object containing the generic parameters, super class and any other generic + * interface parameters. The number of generic interface parameters is equal to the {@code + * expectedNumInterfaces}. + * + *

For example, the signature {@code + * Ljava/lang/Object;Ljava/util/Map;} will + * result in: + * + *

{@code
+   * {
+   *   typeParameters: "",
+   *   superClassSignature: {
+   *     identifier: "Ljava/lang/Object;",
+   *     typeParameters: ""
+   *   },
+   *   interfaceTypeParameters: ["TK;TV;"]
+   * }
+   * }
+ * + * @param name The identifier of the class + * @param signature The class signature as specified in $4.7.9.1 + * @param superName The identifier of the superclass that it extends associated by the {@code + * signature} + * @param interfaces The interfaces that are being implemented by the class associated by the + * {@code signature} + * @return An object containing the generic parameters, super class and any other generic + * interface parameters + */ + static ClassSignature readTypeParametersForInterfaces( + String name, String signature, String superName, String[] interfaces) { + + int expectedNumInterfaces = interfaces.length; + + try { + ClassSignatureParser classSignatureParser = + new ClassSignatureParser(signature, expectedNumInterfaces); + classSignatureParser.readFromSignature(); + + verify( + classSignatureParser.numInterfaces == expectedNumInterfaces, + "Incorrect number of generic parameters parsed. Got %s interfaces, but expected %s", + classSignatureParser.numInterfaces, + expectedNumInterfaces); + + return ClassSignature.create(classSignatureParser); + + } catch (RuntimeException e) { + throw new IllegalArgumentException( + String.format( + "Failed to parse signature %s of class %s with superclass %s and interfaces %s", + signature, name, superName, Arrays.toString(interfaces)), + e); + } + } + + private ClassSignatureParser(String signature, int expectedNumInterfaces) { + this.index = 0; + this.signature = signature; + this.numInterfaces = 0; + this.interfaceTypeParameters = new String[expectedNumInterfaces]; + } + + private void readFromSignature() { + checkState(index == 0, "readFromSignature should only be called once"); + // Generic type signature for any bound types (E.g. ) + processTypeParameters(); + generics = signature.substring(0, index); + + superclassSignature = parseSuperClassTypeSignature(); + + // Superclasses can be inner classes of other types, separated by a '.' + while (signature.charAt(index) == '.') { + index++; + + SuperclassSignature innerClassSignatureInformation = parseSuperClassTypeSignature(); + + // Reconcile the class identifier. We include the typeParameters of the superclass in the + // identifier definition + // of the inner class. Therefore, retrieve that information from the previously obtained + // superclassSignature, while obtaining the typeParameters from + // innerClassSignatureInformation. + superclassSignature = + SuperclassSignature.create( + superclassSignature.identifier() + + superclassSignature.typeParameters() + + "." + + innerClassSignatureInformation.identifier(), + innerClassSignatureInformation.typeParameters()); + } + + checkState( + signature.charAt(index) == ';', + "Expected last \";\" of superclass type definition, but got \"%s\" instead", + signature.charAt(index)); + index++; + + while (index < signature.length()) { + skipPackageSpecifierAndTypeName(); + + // Generic type specifier that we need to copy to our emulated interface definition + if (signature.charAt(index) == '<') { + int startIndexOfTypeParameter = index; + processTypeParameters(); + interfaceTypeParameters[numInterfaces] = + signature.substring(startIndexOfTypeParameter, index); + } else { + // This has no generic specifier, so leave the spot empty to make index traversal easier + // when processing the array + interfaceTypeParameters[numInterfaces] = ""; + } + + checkState( + signature.charAt(index) == ';', + "Expected last \";\" of interface type definition, but got \"%s\" instead", + signature.charAt(index)); + index++; + + numInterfaces++; + } + } + + private SuperclassSignature parseSuperClassTypeSignature() { + int startIndexOfName = index; + skipPackageSpecifierAndTypeName(); + String name = signature.substring(startIndexOfName, index); + + int startIndexOfGenerics = index; + processTypeParameters(); + String generics = signature.substring(startIndexOfGenerics, index); + + return SuperclassSignature.create(name, generics); + } + + private void skipPackageSpecifierAndTypeName() { + while (signature.charAt(index) != ';' && signature.charAt(index) != '<') { + index++; + } + } + + private void processTypeParameters() { + if (signature.charAt(index) == '<') { + int stack = 0; + do { + index++; + // Handle nested typeParameters bounds + if (signature.charAt(index) == '<') { + stack++; + } + } while (signature.charAt(index) != '>' || stack-- > 0); + + checkState( + signature.charAt(index) == '>', + "Expected last \">\" of the type parameter, but got \"%s\" instead", + signature.charAt(index)); + index++; + } + } + + /** + * Container object to hold signature information as specified by $4.7.9.1 + * of the Java Virtual Machine Specification. + */ + @AutoValue + abstract static class ClassSignature { + + /** + * Generic type parameters for this class, if any. It includes all type parameters and + * surrounding angle brackets. + * + * @return Generic type parameters for this class, or an empty String if none exist + */ + abstract String typeParameters(); + + /** + * Signature for the superclass. + * + * @return The signature for the superclass. + */ + abstract SuperclassSignature superClassSignature(); + + /** + * List of generic type parameters for the interfaces the class implements. It includes all type + * parameters for a single interface in 1 String. + * + *

For example, for the signature {@code + * L__desugar__/java/util/List;L__desugar__/java/util/Map;}. the + * interfaceTypeParameters are {@code ["TG;", "TK;TV;"]}. + * + * @return List of type parameters for each interface the class implements. + */ + abstract ImmutableList interfaceTypeParameters(); + + private static ClassSignature create(ClassSignatureParser classSignatureParser) { + return new AutoValue_ClassSignatureParser_ClassSignature( + classSignatureParser.generics, + classSignatureParser.superclassSignature, + ImmutableList.copyOf(classSignatureParser.interfaceTypeParameters)); + } + } + + /** + * Container object to hold signature information about the superclass. Name could include all + * names and generic parameters of its superclasses. Generics will only contain the typeParameters + * of the last class (in the case of inner classes). + */ + @AutoValue + abstract static class SuperclassSignature { + + /** + * Name of the superclass. Includes packagespecifier. + * + * @return The full classname of this superclass. + */ + abstract String identifier(); + + /** + * Generic type parameters for this class, if any. It includes all type parameters and + * surrounding angle brackets. + * + * @return Generic type parameters for this class, or an empty String if none exist + */ + abstract String typeParameters(); + + private static SuperclassSignature create(String name, String generics) { + return new AutoValue_ClassSignatureParser_SuperclassSignature(name, generics); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/ClassVsInterface.java b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassVsInterface.java new file mode 100644 index 00000000000000..00c045cca33bb5 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassVsInterface.java @@ -0,0 +1,70 @@ +// 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.checkState; + +import com.google.devtools.build.android.desugar.io.BitFlags; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.LinkedHashMap; +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 LinkedHashMap known = new LinkedHashMap<>(); + + private final ClassReaderFactory classpath; + + public ClassVsInterface(ClassReaderFactory classpath) { + this.classpath = classpath; + } + + @CanIgnoreReturnValue + 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; + } + + @CanIgnoreReturnValue + 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 = classpath.readIfKnown(outerName); + if (outerClass == null) { + System.err.printf("WARNING: Couldn't find outer class %s of %s%n", outerName, innerName); + // TODO(b/79155927): Make this an error when sources of this problem are fixed. + result = false; // assume it's a class if we can't find it (b/79155927) + } else { + result = BitFlags.isInterface(outerClass.getAccess()); + } + known.put(outerName, result); + } + return result; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java new file mode 100644 index 00000000000000..abd3550b96af50 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CloseResourceMethodScanner.java @@ -0,0 +1,116 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.android.desugar; + +import com.google.common.base.Preconditions; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +/** + * 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.ASM9); + } + + @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.ASM9); + methodSignature = internalName + '.' + name + desc; + } + + @Override + public void visitEnd() { + if (!hasCallToCloseResourceMethod) { + return; + } + if (hasJumpInstructions && !hasStackMapFrame) { + throw new UnsupportedOperationException( + "The method " + + methodSignature + + " calls $closeResource(Throwable, AutoCloseable), " + + "and Desugar thus needs to perform type inference for it " + + "to rewrite $closeResourceMethod. " + + "However, this method has jump instructions, but does not have stack map frames. " + + "Please recompile this class with stack map frames."); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (!hasCallToCloseResourceMethod + && TryWithResourcesRewriter.isCallToSyntheticCloseResource( + internalName, opcode, owner, name, desc)) { + hasCallToCloseResourceMethod = true; + } + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + hasStackMapFrame = true; + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + hasJumpInstructions = true; + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java new file mode 100644 index 00000000000000..660ce5b473dccb --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryInvocationRewriter.java @@ -0,0 +1,104 @@ +// Copyright 2018 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.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * Rewriter of default and static interface methods defined in some core libraries. + * + *

This is conceptually similar to call site rewriting in {@link InterfaceDesugaring} but here + * we're doing it for certain bootclasspath methods and in particular for invokeinterface and + * invokevirtual, which are ignored in regular {@link InterfaceDesugaring}. + */ +public class CoreLibraryInvocationRewriter extends ClassVisitor { + + private final CoreLibrarySupport support; + + public CoreLibraryInvocationRewriter(ClassVisitor cv, CoreLibrarySupport support) { + super(Opcodes.ASM9, cv); + this.support = support; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor result = super.visitMethod(access, name, desc, signature, exceptions); + return result != null ? new CoreLibraryMethodInvocationRewriter(result) : null; + } + + private class CoreLibraryMethodInvocationRewriter extends MethodVisitor { + + public CoreLibraryMethodInvocationRewriter(MethodVisitor mv) { + super(Opcodes.ASM9, mv); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + Class coreInterface = + support.getCoreInterfaceRewritingTarget(opcode, owner, name, desc, itf); + String originalDesc = desc; + if (coreInterface != null) { + String coreInterfaceName = coreInterface.getName().replace('.', '/'); + if (opcode == Opcodes.INVOKESTATIC) { + checkState(owner.equals(coreInterfaceName)); + } else { + desc = InterfaceDesugaring.companionDefaultMethodDescriptor(coreInterfaceName, desc); + } + + if (opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL) { + checkArgument( + itf || opcode == Opcodes.INVOKESPECIAL, + "Expected interface to rewrite %s.%s : %s", + owner, + name, + desc); + if (coreInterface.isInterface()) { + owner = InterfaceDesugaring.getCompanionClassName(coreInterfaceName); + name = + InterfaceDesugaring.normalizeInterfaceMethodName( + name, name.startsWith("lambda$"), opcode); + } else { + owner = checkNotNull(support.getMoveTarget(coreInterfaceName, name, originalDesc)); + } + } else { + checkState(coreInterface.isInterface()); + owner = coreInterfaceName + "$$Dispatch"; + } + + opcode = Opcodes.INVOKESTATIC; + itf = false; + } else { + String newOwner = support.getMoveTarget(owner, name, originalDesc); + if (newOwner != null) { + if (opcode != Opcodes.INVOKESTATIC) { + // assuming a static method + desc = InterfaceDesugaring.companionDefaultMethodDescriptor(owner, desc); + opcode = Opcodes.INVOKESTATIC; + } + owner = newOwner; + itf = false; // assuming a class + } + } + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java new file mode 100644 index 00000000000000..26fec23c013efd --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibrarySupport.java @@ -0,0 +1,600 @@ +// Copyright 2018 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.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static java.util.Collections.unmodifiableSet; +import static java.util.stream.Stream.concat; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; +import com.google.devtools.build.android.desugar.io.BitFlags; +import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MemberUseKind; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import com.google.devtools.build.android.desugar.retarget.ClassMemberRetargetConfig; +import com.google.errorprone.annotations.Immutable; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nullable; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.Remapper; + +/** Helper that keeps track of which core library classes and methods we want to rewrite. */ +class CoreLibrarySupport { + + private static final Object[] EMPTY_FRAME = new Object[0]; + private static final String[] EMPTY_LIST = new String[0]; + + private final CoreLibraryRewriter rewriter; + private final ClassLoader targetLoader; + /** Internal name prefixes that we want to move to a custom package. */ + private final ImmutableSet renamedPrefixes; + + private final ImmutableSet excludeFromEmulation; + /** Internal names of interfaces whose default and static interface methods we'll emulate. */ + private final ImmutableSet> emulatedInterfaces; + + private final ClassMemberRetargetConfig retargetConfig; + + /** ASM {@link Remapper} based on {@link #renamedPrefixes}. */ + private final Remapper corePackageRemapper = + new Remapper() { + @Override + public String map(String typeName) { + return isRenamedCoreLibrary(typeName) ? renameCoreLibrary(typeName) : typeName; + } + }; + + /** For the collection of definitions of emulated default methods (deterministic iteration). */ + private final Multimap emulatedDefaultMethods = + LinkedHashMultimap.create(); + /** Collect targets queried in {@link #getMoveTarget}. */ + private final Set usedRuntimeHelpers = new LinkedHashSet<>(); + + public CoreLibrarySupport( + CoreLibraryRewriter rewriter, + ClassLoader targetLoader, + List renamedPrefixes, + List emulatedInterfaces, + List excludeFromEmulation, + ClassMemberRetargetConfig retargetConfig) { + this.rewriter = rewriter; + this.targetLoader = targetLoader; + this.retargetConfig = retargetConfig; + checkArgument( + renamedPrefixes.stream() + .allMatch( + prefix -> + prefix.startsWith("java/") + || prefix.startsWith("sun/") + || prefix.startsWith("jdk/internal/") + || prefix.startsWith("javadesugar/")), + "Unexpected renamedPrefixes: Actual (%s).", + renamedPrefixes); + this.renamedPrefixes = ImmutableSet.copyOf(renamedPrefixes); + this.excludeFromEmulation = ImmutableSet.copyOf(excludeFromEmulation); + + ImmutableSet.Builder> classBuilder = ImmutableSet.builder(); + for (String itf : emulatedInterfaces) { + checkArgument(itf.startsWith("java/util/"), itf); + Class clazz = loadFromInternal(rewriter.getPrefix() + itf); + checkArgument(clazz.isInterface(), itf); + classBuilder.add(clazz); + } + this.emulatedInterfaces = classBuilder.build(); + } + + public boolean isRenamedCoreLibrary(String internalName) { + String unprefixedName = rewriter.unprefix(internalName); + if (!(unprefixedName.startsWith("java/") + || unprefixedName.startsWith("sun/") + || unprefixedName.startsWith("jdk/internal/") + || unprefixedName.startsWith("javadesugar/")) + || renamedPrefixes.isEmpty()) { + return false; // shortcut + } + // Rename any classes desugar might generate under java/ (for emulated interfaces) as well as + // configured prefixes + return looksGenerated(unprefixedName) + || renamedPrefixes.stream().anyMatch(unprefixedName::startsWith); + } + + public String renameCoreLibrary(String internalName) { + internalName = rewriter.unprefix(internalName); + if (internalName.startsWith("java/")) { + return "j$/" + internalName.substring(/* cut away "java/" prefix */ 5); + } + if (internalName.startsWith("sun/") || internalName.startsWith("jdk/internal")) { + return "j$/" + internalName; + } + if (internalName.startsWith("javadesugar/")) { + return "jd$/" + internalName.substring(/* cut away "javadesugar/" prefix */ 12); + } + + return internalName; + } + + public Remapper getRemapper() { + return corePackageRemapper; + } + + @Nullable + public String getMoveTarget(String owner, String name, String desc) { + final String result; + final MethodKey methodKey = + MethodKey.create(ClassName.create(owner), name, desc) + .acceptTypeMapper(ClassName.IN_PROCESS_LABEL_STRIPPER); + MethodInvocationSite replacementSite = + retargetConfig.findReplacementSite( + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKEVIRTUAL) + .setIsInterface(false) + .setMethod(methodKey.acceptTypeMapper(ClassName.IN_PROCESS_LABEL_STRIPPER)) + .build()); + if (replacementSite == null) { + result = null; + } else { + MethodKey targetMethod = + replacementSite.acceptTypeMapper(ClassName.SHADOWED_TO_MIRRORED_TYPE_MAPPER).method(); + result = targetMethod.ownerName(); + } + + if (result != null) { + // Remember that we need the move target so we can include it in the output later + usedRuntimeHelpers.add(result); + } + return result; + } + + /** + * Returns {@code true} for java.* classes or interfaces that are subtypes of emulated interfaces. + * Note that implies that this method always returns {@code false} for user-written classes. + */ + public boolean isEmulatedCoreClassOrInterface(String internalName) { + return getEmulatedCoreClassOrInterface(internalName) != null; + } + + /** Includes the given method definition in any applicable core interface emulation logic. */ + public void registerIfEmulatedCoreInterface( + int access, String owner, String name, String desc, String[] exceptions) { + Class emulated = getEmulatedCoreClassOrInterface(owner); + if (emulated == null) { + return; + } + checkArgument(emulated.isInterface(), "Shouldn't be called for a class: %s.%s", owner, name); + checkArgument( + BitFlags.noneSet( + access, + Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE), + "Should only be called for default methods: %s.%s", + owner, + name); + emulatedDefaultMethods.put( + name + ":" + desc, EmulatedMethod.create(access, emulated, name, desc, exceptions)); + } + + /** + * If the given invocation needs to go through a companion class of an emulated or renamed core + * interface, this methods returns that interface. This is a helper method for {@link + * CoreLibraryInvocationRewriter}. + * + *

This method can only return non-{@code null} if {@code owner} is a core library type. It + * usually returns an emulated interface, unless the given invocation is a super-call to a core + * class's implementation of an emulated method that's being moved (other implementations of + * emulated methods in core classes are ignored). In that case the class is returned and the + * caller can use {@link #getMoveTarget} to find out where to redirect the invokespecial to. + */ + // TODO(kmb): Rethink this API and consider combining it with getMoveTarget(). + @Nullable + public Class getCoreInterfaceRewritingTarget( + int opcode, String owner, String name, String desc, boolean itf) { + if (looksGenerated(owner)) { + // Regular desugaring handles generated classes, no emulation is needed + return null; + } + if (!itf && opcode == Opcodes.INVOKESTATIC) { + // Ignore static invocations on classes--they never need rewriting (unless moved but that's + // handled separately). + return null; + } + if ("".equals(name)) { + return null; // Constructors aren't rewritten + } + + Class clazz; + if (isRenamedCoreLibrary(owner)) { + // For renamed invocation targets we just need to do what InterfaceDesugaring does, that is, + // only worry about invokestatic and invokespecial interface invocations; nothing to do for + // classes and invokeinterface. InterfaceDesugaring ignores bootclasspath interfaces, + // so we have to do its work here for renamed interfaces. + if (itf && (opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL)) { + clazz = loadFromInternal(owner); + } else { + return null; + } + } else { + // If not renamed, see if the owner needs emulation. + clazz = getEmulatedCoreClassOrInterface(owner); + if (clazz == null) { + return null; + } + } + checkArgument(itf == clazz.isInterface(), "%s expected to be interface: %s", owner, itf); + + if (opcode == Opcodes.INVOKESTATIC) { + // Static interface invocation always goes to the given owner + checkState(itf); // we should've bailed out above. + return clazz; + } + + // See if the invoked method is a default method, which will need rewriting. For invokespecial + // we can only get here if its a default method, and invokestatic we handled above. + Method callee = findInterfaceMethod(clazz, name, desc); + if (callee != null && callee.isDefault()) { + if (isExcluded(callee)) { + return null; + } + + if (!itf && opcode == Opcodes.INVOKESPECIAL) { + // See if the invoked implementation is moved; note we ignore all other overrides in classes + Class impl = clazz; // we know clazz is not an interface because !itf + while (impl != null) { + String implName = impl.getName().replace('.', '/'); + if (getMoveTarget(implName, name, desc) != null) { + return impl; + } + impl = impl.getSuperclass(); + } + } + + Class result = callee.getDeclaringClass(); + if (isRenamedCoreLibrary(result.getName().replace('.', '/')) + || emulatedInterfaces.stream().anyMatch(emulated -> emulated.isAssignableFrom(result))) { + return result; + } + // We get here if the declaring class is a supertype of an emulated interface. In that case + // use the emulated interface instead (since we don't desugar the supertype). Fail in case + // there are multiple possibilities. + Iterator> roots = + emulatedInterfaces.stream() + .filter( + emulated -> emulated.isAssignableFrom(clazz) && result.isAssignableFrom(emulated)) + .iterator(); + checkState(roots.hasNext()); // must exist + Class substitute = roots.next(); + checkState(!roots.hasNext(), "Ambiguous emulation substitute: %s", callee); + return substitute; + } else { + checkArgument( + !itf || opcode != Opcodes.INVOKESPECIAL, + "Couldn't resolve interface super call %s.super.%s : %s", + owner, + name, + desc); + } + return null; + } + + /** + * Returns the given class if it's a core library class or interface with emulated default + * methods. This is equivalent to calling {@link #isEmulatedCoreClassOrInterface} and then just + * loading the class (using the target class loader). + */ + public Class getEmulatedCoreClassOrInterface(String internalName) { + if (looksGenerated(internalName)) { + // Regular desugaring handles generated classes, no emulation is needed + return null; + } + { + String unprefixedOwner = rewriter.unprefix(internalName); + if (!unprefixedOwner.startsWith("java/util/") || isRenamedCoreLibrary(unprefixedOwner)) { + return null; + } + } + + Class clazz = loadFromInternal(internalName); + if (emulatedInterfaces.stream().anyMatch(itf -> itf.isAssignableFrom(clazz))) { + return clazz; + } + return null; + } + + /** Returns targets queried in {@link #getMoveTarget}. */ + public Set usedRuntimeHelpers() { + return unmodifiableSet(usedRuntimeHelpers); + } + + public void makeDispatchHelpers(GeneratedClassStore store) { + LinkedHashMap, ClassVisitor> dispatchHelpers = new LinkedHashMap<>(); + for (Collection group : emulatedDefaultMethods.asMap().values()) { + checkState(!group.isEmpty()); + Class root = + group.stream() + .map(EmulatedMethod::owner) + .max(DefaultMethodClassFixer.SubtypeComparator.INSTANCE) + .get(); + checkState( + group.stream().map(m -> m.owner()).allMatch(o -> root.isAssignableFrom(o)), + "Not a single unique method: %s", + group); + String methodName = group.stream().findAny().get().name(); + + ImmutableList> customOverrides = findCustomOverrides(root, methodName); + + for (EmulatedMethod methodDefinition : group) { + Class owner = methodDefinition.owner(); + ClassVisitor dispatchHelper = + dispatchHelpers.computeIfAbsent( + owner, + clazz -> { + String className = clazz.getName().replace('.', '/') + "$$Dispatch"; + ClassVisitor result = store.add(className); + result.visit( + Opcodes.V1_7, + // Must be public so dispatch methods can be called from anywhere + Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC, + className, + /*signature=*/ null, + "java/lang/Object", + EMPTY_LIST); + return result; + }); + + // Types to check for before calling methodDefinition's companion, sub- before super-types + ImmutableList> typechecks = + concat(group.stream().map(EmulatedMethod::owner), customOverrides.stream()) + .filter(o -> o != owner && owner.isAssignableFrom(o)) + .distinct() // should already be but just in case + .sorted(DefaultMethodClassFixer.SubtypeComparator.INSTANCE) + .collect(ImmutableList.toImmutableList()); + makeDispatchHelperMethod(dispatchHelper, methodDefinition, typechecks); + } + } + } + + private ImmutableList> findCustomOverrides(Class root, String methodName) { + ImmutableList.Builder> customOverrides = ImmutableList.builder(); + for (Map.Entry move : + retargetConfig.invocationReplacements().entrySet()) { + // move.getKey is a string # which we validated in the constructor. + // We need to take the string apart here to compare owner and name separately. + if (!methodName.equals(move.getKey().method().name())) { + continue; + } + Class target = loadFromInternal(rewriter.getPrefix() + move.getKey().method().ownerName()); + if (!root.isAssignableFrom(target)) { + continue; + } + checkState(!target.isInterface(), "can't move emulated interface method: %s", move); + customOverrides.add(target); + } + return customOverrides.build(); + } + + private void makeDispatchHelperMethod( + ClassVisitor helper, EmulatedMethod method, ImmutableList> typechecks) { + checkArgument(method.owner().isInterface()); + String owner = method.owner().getName().replace('.', '/'); + Type methodType = Type.getMethodType(method.descriptor()); + String companionDesc = + InterfaceDesugaring.companionDefaultMethodDescriptor(owner, method.descriptor()); + MethodVisitor dispatchMethod = + helper.visitMethod( + method.access() | Opcodes.ACC_STATIC, + method.name(), + companionDesc, + /*signature=*/ null, // signature is invalid due to extra "receiver" argument + method.exceptions().toArray(EMPTY_LIST)); + + dispatchMethod.visitCode(); + { + // See if the receiver might come with its own implementation of the method, and call it. + // We do this by testing for the interface type created by EmulatedInterfaceRewriter + Label fallthrough = new Label(); + String emulationInterface = renameCoreLibrary(owner); + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.INSTANCEOF, emulationInterface); + dispatchMethod.visitJumpInsn(Opcodes.IFEQ, fallthrough); + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.CHECKCAST, emulationInterface); + + visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */); + dispatchMethod.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + emulationInterface, + method.name(), + method.descriptor(), + /*isInterface=*/ true); + dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN)); + + dispatchMethod.visitLabel(fallthrough); + // Trivial frame for the branch target: same empty stack as before + dispatchMethod.visitFrame(Opcodes.F_SAME, 0, EMPTY_FRAME, 0, EMPTY_FRAME); + } + + // Next, check for subtypes with specialized implementations and call them + for (Class tested : typechecks) { + Label fallthrough = new Label(); + String testedName = tested.getName().replace('.', '/'); + + // In case of a class this must be a member move; for interfaces use the companion. + final String target; + String calledMethod = method.name(); + if (tested.isInterface()) { + target = InterfaceDesugaring.getCompanionClassName(testedName); + calledMethod += InterfaceDesugaring.DEFAULT_COMPANION_METHOD_SUFFIX; + } else { + MethodKey targetMethod = + checkNotNull( + retargetConfig.findReplacementSite( + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKEVIRTUAL) + .setIsInterface(false) + .setMethod( + MethodKey.create( + ClassName.create(testedName), + method.name(), + method.descriptor()) + .acceptTypeMapper(ClassName.IN_PROCESS_LABEL_STRIPPER)) + .build())) + .acceptTypeMapper(ClassName.SHADOWED_TO_MIRRORED_TYPE_MAPPER) + .method(); + target = targetMethod.ownerName(); + } + + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.INSTANCEOF, testedName); + dispatchMethod.visitJumpInsn(Opcodes.IFEQ, fallthrough); + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + dispatchMethod.visitTypeInsn(Opcodes.CHECKCAST, testedName); // make verifier happy + + visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */); + dispatchMethod.visitMethodInsn( + Opcodes.INVOKESTATIC, + target, + calledMethod, + InterfaceDesugaring.companionDefaultMethodDescriptor(testedName, method.descriptor()), + /*isInterface=*/ false); + dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN)); + + dispatchMethod.visitLabel(fallthrough); + // Trivial frame for the branch target: same empty stack as before + dispatchMethod.visitFrame(Opcodes.F_SAME, 0, EMPTY_FRAME, 0, EMPTY_FRAME); + } + + // Call static type's default implementation in companion class + dispatchMethod.visitVarInsn(Opcodes.ALOAD, 0); // load "receiver" + visitLoadArgs(dispatchMethod, methodType, 1 /* receiver already loaded above */); + dispatchMethod.visitMethodInsn( + Opcodes.INVOKESTATIC, + InterfaceDesugaring.getCompanionClassName(owner), + method.name() + InterfaceDesugaring.DEFAULT_COMPANION_METHOD_SUFFIX, + companionDesc, + /*isInterface=*/ false); + dispatchMethod.visitInsn(methodType.getReturnType().getOpcode(Opcodes.IRETURN)); + + dispatchMethod.visitMaxs(0, 0); + dispatchMethod.visitEnd(); + } + + private boolean isExcluded(Method method) { + String unprefixedOwner = + rewriter.unprefix(method.getDeclaringClass().getName().replace('.', '/')); + return excludeFromEmulation.contains(unprefixedOwner + "#" + method.getName()); + } + + private Class loadFromInternal(String internalName) { + try { + return targetLoader.loadClass(internalName.replace('/', '.')); + } catch (ClassNotFoundException e) { + throw (NoClassDefFoundError) new NoClassDefFoundError().initCause(e); + } + } + + private static Method findInterfaceMethod(Class clazz, String name, String desc) { + return collectImplementedInterfaces(clazz, new LinkedHashSet<>()).stream() + // search more subtypes before supertypes + .sorted(DefaultMethodClassFixer.SubtypeComparator.INSTANCE) + .map(itf -> findMethod(itf, name, desc)) + .filter(Objects::nonNull) + .findFirst() + .orElse((Method) null); + } + + private static Method findMethod(Class clazz, String name, String desc) { + for (Method m : clazz.getMethods()) { + if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) { + return m; + } + } + return null; + } + + private static Set> collectImplementedInterfaces(Class clazz, Set> dest) { + if (clazz.isInterface()) { + if (!dest.add(clazz)) { + return dest; + } + } else if (clazz.getSuperclass() != null) { + collectImplementedInterfaces(clazz.getSuperclass(), dest); + } + + for (Class itf : clazz.getInterfaces()) { + collectImplementedInterfaces(itf, dest); + } + return dest; + } + + /** + * Emits instructions to load a method's parameters as arguments of a method call assumed to have + * compatible descriptor, starting at the given local variable slot. + */ + private static void visitLoadArgs(MethodVisitor dispatchMethod, Type neededType, int slot) { + for (Type arg : neededType.getArgumentTypes()) { + dispatchMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); + slot += arg.getSize(); + } + } + + /** Checks whether the given class is (likely) generated by desugar itself. */ + private static boolean looksGenerated(String owner) { + return owner.contains("$$Lambda$") + || owner.endsWith("$$CC") + || owner.endsWith("$NestCC") + || owner.endsWith("$$Dispatch"); + } + + @AutoValue + @Immutable + abstract static class EmulatedMethod { + public static EmulatedMethod create( + int access, Class owner, String name, String desc, @Nullable String[] exceptions) { + return new AutoValue_CoreLibrarySupport_EmulatedMethod( + access, + owner, + name, + desc, + exceptions != null ? ImmutableList.copyOf(exceptions) : ImmutableList.of()); + } + + abstract int access(); + + abstract Class owner(); + + abstract String name(); + + abstract String descriptor(); + + abstract ImmutableList exceptions(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CorePackageRenamer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CorePackageRenamer.java new file mode 100644 index 00000000000000..93ab650b329bfa --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CorePackageRenamer.java @@ -0,0 +1,123 @@ +// Copyright 2018 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.checkState; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.MethodRemapper; +import org.objectweb.asm.commons.Remapper; + +/** + * A visitor that renames packages so configured using {@link CoreLibrarySupport}. Additionally + * generate bridge-like methods for core library overrides that should be preserved that call the + * renamed variants. + */ +class CorePackageRenamer extends ClassRemapper { + + private String internalName; + + public CorePackageRenamer(ClassVisitor cv, CoreLibrarySupport support) { + super(cv, new CorePackageRemapper(support)); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkState(internalName == null || internalName.equals(name), "Instance already used."); + internalName = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + protected CoreMethodRemapper createMethodRemapper(MethodVisitor methodVisitor) { + return new CoreMethodRemapper(methodVisitor, remapper); + } + + private class CoreMethodRemapper extends MethodRemapper { + + public CoreMethodRemapper(MethodVisitor methodVisitor, Remapper remapper) { + super(methodVisitor, remapper); + } + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String descriptor, boolean isInterface) { + CorePackageRemapper remapper = (CorePackageRemapper) this.remapper; + remapper.didSomething = false; + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + // TODO(b/79121791): Make this more precise: look for all unsupported core library members + checkState( + !remapper.didSomething + || !owner.startsWith("android/") + || owner.startsWith("android/arch/") + || owner.startsWith("android/device/collectors/") // b/179812549 + || owner.startsWith("android/support/"), + "%s calls %s.%s%s which is not supported with core library desugaring. Please file " + + "a feature request to support this method", + internalName, + owner, + name, + descriptor); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + CorePackageRemapper remapper = (CorePackageRemapper) this.remapper; + remapper.didSomething = false; + super.visitFieldInsn(opcode, owner, name, descriptor); + // TODO(b/79121791): Make this more precise: look for all unsupported core library members + checkState( + !remapper.didSomething + || !owner.startsWith("android/") + || owner.startsWith("android/arch/") + || owner.startsWith("android/car/") + || owner.startsWith("android/device/collectors/") // b/179812549 + || owner.startsWith("android/support/"), + "%s accesses %s.%s: %s which is not supported with core library desugaring. Please file " + + "a feature request to support this field", + internalName, + owner, + name, + descriptor); + } + } + + /** ASM {@link Remapper} based on {@link CoreLibrarySupport}. */ + private static class CorePackageRemapper extends Remapper { + + private final CoreLibrarySupport support; + boolean didSomething = false; + + CorePackageRemapper(CoreLibrarySupport support) { + this.support = support; + } + + @Override + public String map(String typeName) { + if (support.isRenamedCoreLibrary(typeName)) { + didSomething = true; + return support.renameCoreLibrary(typeName); + } + return typeName; + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java new file mode 100644 index 00000000000000..d710a644a17108 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -0,0 +1,1001 @@ +// 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.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedSet; +import com.google.devtools.build.android.desugar.io.BitFlags; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.r8.DependencyCollector; +import org.objectweb.asm.Opcodes; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.TreeMap; +import javax.annotation.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Fixer of classes that extend interfaces with default methods to declare any missing methods + * explicitly and call the corresponding companion method generated by {@link InterfaceDesugaring}. + */ +public class DefaultMethodClassFixer extends ClassVisitor { + + private final boolean useGeneratedBaseClasses; + private final ClassReaderFactory classpath; + private final ClassReaderFactory bootclasspath; + private final ClassLoader targetLoader; + private final DependencyCollector depsCollector; + @Nullable private final CoreLibrarySupport coreLibrarySupport; + private final LinkedHashSet instanceMethods = new LinkedHashSet<>(); + + private boolean isInterface; + private String internalName; + private ImmutableList directInterfaces; + private String superName; + /** Interface whose companion was chosen as base class, if any. */ + @Nullable private Class newSuperName; + /** This method node caches , and flushes out in {@code visitEnd()}; */ + private MethodNode clInitMethodNode; + + private ImmutableSortedSet> interfacesToStub; + + public DefaultMethodClassFixer( + ClassVisitor dest, + boolean useGeneratedBaseClasses, + ClassReaderFactory classpath, + DependencyCollector depsCollector, + @Nullable CoreLibrarySupport coreLibrarySupport, + ClassReaderFactory bootclasspath, + ClassLoader targetLoader) { + super(Opcodes.ASM9, dest); + this.useGeneratedBaseClasses = useGeneratedBaseClasses; + this.classpath = classpath; + this.coreLibrarySupport = coreLibrarySupport; + this.bootclasspath = bootclasspath; + this.targetLoader = targetLoader; + this.depsCollector = depsCollector; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkState(this.directInterfaces == null); + isInterface = BitFlags.isSet(access, Opcodes.ACC_INTERFACE); + internalName = name; + checkArgument( + superName != null || "java/lang/Object".equals(name), // ASM promises this + "Type without superclass: %s", + name); + this.directInterfaces = ImmutableList.copyOf(interfaces); + this.superName = superName; + + if (!isInterface + && (mayNeedInterfaceStubsForEmulatedSuperclass() + || defaultMethodsDefined(directInterfaces))) { + TreeMap, Long> allInterfaces = new TreeMap<>(SubtypeComparator.INSTANCE); + for (String direct : interfaces) { + if (InterfaceDesugaring.getCompanionClassName(direct).equals(name)) { + checkState(useGeneratedBaseClasses, "%s shouldn't implement %s", name, direct); + continue; // InterfaceDesugaring already made stubs for this interface + } + // Loading ensures all transitively implemented interfaces can be loaded, which is necessary + // to produce correct default method stubs in all cases. We could do without classloading + // but it's convenient to rely on Class.isAssignableFrom to compute subtype relationships, + // and we'd still have to insist that all transitively implemented interfaces can be loaded. + // We don't load the visited class, however, in case it's generated. + Class itf = loadFromInternal(direct); + collectInterfaces(itf, allInterfaces); + } + if (mayNeedInterfaceStubsForEmulatedSuperclass()) { + // Collect interfaces inherited from emulated superclasses as well, to handle things like + // extending AbstractList without explicitly implementing List. + for (Class clazz = loadFromInternal(superName); + clazz != null; + clazz = clazz.getSuperclass()) { + for (Class itf : clazz.getInterfaces()) { + collectInterfaces(itf, allInterfaces); + } + } + } + + if (useGeneratedBaseClasses && "java/lang/Object".equals(superName)) { + // If the class directly extends Object, use the generated base class for the interface with + // the most default methods instead. This can help avoid stubbed default methods when + // they're already defined in the base class. + // This is an imperfect heuristic, not least b/c we don't know yet what methods would need + // to be stubbed, but we decide this here up-front b/c that lets us fix up the class's + // constructors' super calls when we see them. + // Note inherited default methods are included in the count, so we will choose subtypes over + // supertypes, which is desirable. + long maxBaseMethodCount = 0; + for (Map.Entry, Long> itf : allInterfaces.entrySet()) { + if (itf.getValue() > maxBaseMethodCount) { + maxBaseMethodCount = itf.getValue(); + newSuperName = itf.getKey(); + superName = InterfaceDesugaring.getCompanionClassName(internalName(itf.getKey())); + signature = null; // Changing superclass invalidates signature + } + } + if (newSuperName != null && !bootclasspath.isKnown(internalName(newSuperName))) { + // Record dependency on the chosen companion class + depsCollector.assumeCompanionClass( + internalName, InterfaceDesugaring.getCompanionClassName(internalName(newSuperName))); + } + } + interfacesToStub = ImmutableSortedSet.copyOfSorted(allInterfaces.navigableKeySet()); + } else { + interfacesToStub = ImmutableSortedSet.of(); + } + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitEnd() { + if (!interfacesToStub.isEmpty()) { + // Inherited methods take precedence over default methods, so visit all superclasses and + // figure out what methods they declare before stubbing in any missing default methods. + recordInheritedMethods(); + stubMissingDefaultAndBridgeMethods(); + // Check whether there are interfaces with default methods and . If yes, the following + // method call will return a list of interface fields to access in the to trigger + // the initialization of these interfaces. + ImmutableList companionsToTriggerInterfaceClinit = + collectOrderedCompanionsToTriggerInterfaceClinit(directInterfaces); + if (!companionsToTriggerInterfaceClinit.isEmpty()) { + if (clInitMethodNode == null) { + clInitMethodNode = new MethodNode(Opcodes.ACC_STATIC, "", "()V", null, null); + } + desugarClinitToTriggerInterfaceInitializers(companionsToTriggerInterfaceClinit); + } + } + if (clInitMethodNode != null && super.cv != null) { // Write to the chained visitor. + clInitMethodNode.accept(super.cv); + } + super.visitEnd(); + } + + private boolean isClinitAlreadyDesugared( + ImmutableList companionsToAccessToTriggerInterfaceClinit) { + InsnList instructions = clInitMethodNode.instructions; + if (instructions.size() <= companionsToAccessToTriggerInterfaceClinit.size()) { + // The must end with RETURN, so if the instruction count is less than or equal to + // the companion class count, this has not been desugared. + return false; + } + Iterator iterator = instructions.iterator(); + for (String companion : companionsToAccessToTriggerInterfaceClinit) { + if (!iterator.hasNext()) { + return false; + } + AbstractInsnNode insn = iterator.next(); + if (!(insn instanceof MethodInsnNode)) { + return false; + } + MethodInsnNode methodInsnNode = (MethodInsnNode) insn; + if (methodInsnNode.getOpcode() != Opcodes.INVOKESTATIC + || !methodInsnNode.owner.equals(companion) + || !methodInsnNode.name.equals( + InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME)) { + return false; + } + checkState( + methodInsnNode.desc.equals( + InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC), + "Inconsistent method desc: %s vs %s", + methodInsnNode.desc, + InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC); + } + return true; + } + + private void desugarClinitToTriggerInterfaceInitializers( + ImmutableList companionsToTriggerInterfaceClinit) { + if (isClinitAlreadyDesugared(companionsToTriggerInterfaceClinit)) { + return; + } + InsnList desugarInsts = new InsnList(); + for (String companionClass : companionsToTriggerInterfaceClinit) { + desugarInsts.add( + new MethodInsnNode( + Opcodes.INVOKESTATIC, + companionClass, + InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME, + InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC, + false)); + } + if (clInitMethodNode.instructions.size() == 0) { + clInitMethodNode.instructions.insert(new InsnNode(Opcodes.RETURN)); + } + clInitMethodNode.instructions.insertBefore( + clInitMethodNode.instructions.getFirst(), desugarInsts); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + // Keep track of instance methods implemented in this class for later. + if (!isInterface) { + recordIfInstanceMethod(access, name, desc); + } + if ("".equals(name)) { + checkState(clInitMethodNode == null, "This class fixer has been used. "); + clInitMethodNode = new MethodNode(access, name, desc, signature, exceptions); + return clInitMethodNode; + } + + MethodVisitor dest = super.visitMethod(access, name, desc, signature, exceptions); + if (newSuperName != null && "".equals(name)) { + // Since we changed the base class we need to fix up super constructor calls. + return new ConstructorFixer( + dest, InterfaceDesugaring.getCompanionClassName(internalName(newSuperName))); + } + return dest; + } + + private boolean mayNeedInterfaceStubsForEmulatedSuperclass() { + return coreLibrarySupport != null + && !coreLibrarySupport.isEmulatedCoreClassOrInterface(internalName) + && coreLibrarySupport.isEmulatedCoreClassOrInterface(superName); + } + + private void stubMissingDefaultAndBridgeMethods() { + Class superclass = loadFromInternal(superName); + boolean mayNeedStubsForSuperclass = mayNeedInterfaceStubsForEmulatedSuperclass(); + for (Class interfaceToVisit : interfacesToStub) { + // if J extends I, J is allowed to redefine I's default methods. The comparator we used + // above makes sure we visit J before I in that case so we can use J's definition. + if (!mayNeedStubsForSuperclass && interfaceToVisit.isAssignableFrom(superclass)) { + // superclass is also rewritten and already implements this interface, so we _must_ skip it. + continue; + } + stubMissingDefaultAndBridgeMethods(internalName(interfaceToVisit), mayNeedStubsForSuperclass); + } + } + + private void stubMissingDefaultAndBridgeMethods( + String implemented, boolean mayNeedStubsForSuperclass) { + ClassReader bytecode; + boolean isBootclasspath; + if (bootclasspath.isKnown(implemented)) { + if (coreLibrarySupport != null + && (coreLibrarySupport.isRenamedCoreLibrary(implemented) + || coreLibrarySupport.isEmulatedCoreClassOrInterface(implemented))) { + bytecode = checkNotNull(bootclasspath.readIfKnown(implemented), implemented); + isBootclasspath = true; + } else { + // Default methods from interfaces on the bootclasspath that we're not renaming or emulating + // are assumed available at runtime, so just ignore them. + return; + } + } else { + bytecode = + checkNotNull( + classpath.readIfKnown(implemented), + "Couldn't find interface %s implemented by %s", + implemented, + internalName); + isBootclasspath = false; + } + bytecode.accept( + new DefaultMethodStubber(isBootclasspath, mayNeedStubsForSuperclass), + ClassReader.SKIP_DEBUG); + } + + private Class loadFromInternal(String internalName) { + try { + return targetLoader.loadClass(internalName.replace('/', '.')); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + throw new IllegalStateException( + "Couldn't load " + internalName + " to stub default methods for " + this.internalName, e); + } + } + + /** Returns the internal name used for this class in bytecode. */ + static String internalName(Class clazz) { + return clazz.getName().replace('.', '/'); + } + + private void collectInterfaces(Class itf, Map, Long> dest) { + checkArgument(itf.isInterface()); + if (dest.containsKey(itf)) { + return; + } + if (useGeneratedBaseClasses && hasCompanionClass(itf)) { + // Count inherited and declared default methods + long defaultMethodCount = Arrays.stream(itf.getMethods()).filter(Method::isDefault).count(); + dest.put(itf, defaultMethodCount); + } else { + // Use -1 if there's no base class we could use. This makes sure we don't try to use a + // companion class as base class that doesn't exist. + dest.put(itf, -1L); + } + for (Class implemented : itf.getInterfaces()) { + collectInterfaces(implemented, dest); + } + } + + private boolean hasCompanionClass(Class itf) { + checkArgument(itf.isInterface()); + if (Arrays.stream(itf.getDeclaredMethods()).noneMatch(m -> m.isDefault() && !m.isBridge())) { + return false; + } + String implemented = internalName(itf); + if (implemented.startsWith("java/") + || implemented.startsWith(ClassName.IN_PROCESS_LABEL + "java/")) { + return coreLibrarySupport != null + && (coreLibrarySupport.isRenamedCoreLibrary(implemented) + || coreLibrarySupport.isEmulatedCoreClassOrInterface(implemented)); + } else { + return !bootclasspath.isKnown(implemented); + } + } + + private void recordInheritedMethods() { + InstanceMethodRecorder recorder = + new InstanceMethodRecorder(mayNeedInterfaceStubsForEmulatedSuperclass()); + if (newSuperName != null) { + checkState(useGeneratedBaseClasses); + // If we're using a generated base class, walk the implemented interfaces and record default + // methods we won't need to stub. That reflects the methods that will be in the generated + // base class (and its superclasses, if applicable), without looking at the generated class. + // We go through sub-interfaces before their super-interfaces (same order as when we stub) + // to encounter overriding before overridden default methods. We don't record methods from + // interfaces the chosen base class doesn't implement, even if those methods also appear in + // interfaces we would normally record later. That ensures we generate a stub when a default + // method is available in the base class but needs to be overridden due to an overriding + // default method in a sub-interface not implemented by the base class. + LinkedHashSet allSeen = new LinkedHashSet<>(); + for (Class itf : interfacesToStub) { + boolean willBeInBaseClass = itf.isAssignableFrom(newSuperName); + for (Method m : itf.getDeclaredMethods()) { + if (!m.isDefault()) { + continue; + } + String desc = Type.getMethodDescriptor(m); + if (coreLibrarySupport != null) { + // Foreshadow any type renaming to avoid issues with double-desugaring (b/111447199) + desc = coreLibrarySupport.getRemapper().mapMethodDesc(desc); + } + String methodKey = m.getName() + ":" + desc; + if (allSeen.add(methodKey) && willBeInBaseClass) { + // Only record never-seen methods, and only for super-types of newSuperName (see longer + // explanation above) + instanceMethods.add(methodKey); + } + } + } + + // Fall through to the logic below to record j.l.Object's methods. + checkState(superName.equals("java/lang/Object")); + } + + // Walk superclasses + String internalName = superName; + while (internalName != null) { + ClassReader bytecode = bootclasspath.readIfKnown(internalName); + if (bytecode == null) { + bytecode = + checkNotNull( + classpath.readIfKnown(internalName), "Superclass not found: %s", internalName); + } + bytecode.accept(recorder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG); + internalName = bytecode.getSuperName(); + } + } + + /** + * Starting from the given interfaces, this method scans the interface hierarchy, finds the + * interfaces that have default methods and , and returns the companion class names of + * these interfaces. + * + *

Note that the returned companion classes are ordered in the order of the interface + * initialization, which is consistent with the JVM behavior. For example, "class A implements I1, + * I2", the returned list would be [I1$$CC, I2$$CC], not [I2$$CC, I1$$CC]. + */ + private ImmutableList collectOrderedCompanionsToTriggerInterfaceClinit( + ImmutableList interfaces) { + ImmutableList.Builder companionCollector = ImmutableList.builder(); + LinkedHashSet visitedInterfaces = new LinkedHashSet<>(); + for (String anInterface : interfaces) { + collectOrderedCompanionsToTriggerInterfaceClinit( + anInterface, visitedInterfaces, companionCollector); + } + return companionCollector.build(); + } + + private void collectOrderedCompanionsToTriggerInterfaceClinit( + String anInterface, + LinkedHashSet visitedInterfaces, + ImmutableList.Builder companionCollector) { + if (!visitedInterfaces.add(anInterface)) { + return; + } + ClassReader bytecode = classpath.readIfKnown(anInterface); + if (bytecode == null || bootclasspath.isKnown(anInterface)) { + return; + } + String[] parentInterfaces = bytecode.getInterfaces(); + if (parentInterfaces != null && parentInterfaces.length > 0) { + for (String parentInterface : parentInterfaces) { + collectOrderedCompanionsToTriggerInterfaceClinit( + parentInterface, visitedInterfaces, companionCollector); + } + } + InterfaceInitializationNecessityDetector necessityDetector = + new InterfaceInitializationNecessityDetector(bytecode.getClassName()); + bytecode.accept(necessityDetector, ClassReader.SKIP_DEBUG); + if (necessityDetector.needsToInitialize()) { + // If we need to initialize this interface, we initialize its companion class, and its + // companion class will initialize the interface then. This desigin decision is made to avoid + // access issue, e.g., package-private interfaces. + companionCollector.add(InterfaceDesugaring.getCompanionClassName(anInterface)); + } + } + + /** + * Recursively searches the given interfaces for default methods not implemented by this class + * directly. If this method returns true we need to think about stubbing missing default methods. + */ + private boolean defaultMethodsDefined(ImmutableList interfaces) { + for (String implemented : interfaces) { + if (useGeneratedBaseClasses + && InterfaceDesugaring.getCompanionClassName(implemented).equals(internalName)) { + continue; // InterfaceDesugaring already made stubs for this interface + } + ClassReader bytecode; + if (bootclasspath.isKnown(implemented)) { + if (coreLibrarySupport != null + && coreLibrarySupport.isEmulatedCoreClassOrInterface(implemented)) { + return true; // need to stub in emulated interface methods such as Collection.stream() + } else if (coreLibrarySupport != null + && coreLibrarySupport.isRenamedCoreLibrary(implemented)) { + // Check default methods of renamed interfaces + bytecode = checkNotNull(bootclasspath.readIfKnown(implemented), implemented); + } else { + continue; + } + } else { + bytecode = classpath.readIfKnown(implemented); + if (bytecode == null) { + // Interface isn't on the classpath, which indicates incomplete classpaths. Record missing + // dependency so we can check it later. If we don't check then we may get runtime + // failures or wrong behavior from default methods that should've been stubbed in. + // TODO(kmb): Print a warning so people can start fixing their deps? + depsCollector.missingImplementedInterface(internalName, implemented); + continue; + } + } + + // Class in classpath and bootclasspath is a bad idea but in any event, assume the + // bootclasspath will take precedence like in a classloader. + // We can skip code attributes as we just need to find default methods to stub. + DefaultMethodFinder finder = new DefaultMethodFinder(); + bytecode.accept(finder, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG); + if (finder.foundDefaultMethods()) { + return true; + } + } + return false; + } + + /** Returns {@code true} for non-bridge default methods not in {@link #instanceMethods}. */ + private boolean shouldStubAsDefaultMethod(int access, String name, String desc) { + // Ignore private methods, which technically aren't default methods and can only be called from + // other methods defined in the interface. This also ignores lambda body methods, which is fine + // as we don't want or need to stub those. Also ignore bridge methods as javac adds them to + // concrete classes as needed anyway and we handle them separately for generated lambda classes. + // Note that an exception is that, if a bridge method is for a default interface method, javac + // will NOT generate the bridge method in the implementing class. So we need extra logic to + // handle these bridge methods. + return isNonBridgeDefaultMethod(access) && !recordedInstanceMethod(name, desc); + } + + private static boolean isNonBridgeDefaultMethod(int access) { + return BitFlags.noneSet( + access, + Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC | Opcodes.ACC_BRIDGE | Opcodes.ACC_PRIVATE); + } + + /** + * Check whether an interface method is a bridge method for a default interface method. This type + * of bridge methods is special, as they are not put in the implementing classes by javac. + */ + private boolean shouldStubAsBridgeDefaultMethod(int access, String name, String desc) { + return BitFlags.isSet(access, Opcodes.ACC_BRIDGE | Opcodes.ACC_PUBLIC) + && BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC) + && !recordedInstanceMethod(name, desc); + } + + private void recordIfInstanceMethod(int access, String name, String desc) { + if (BitFlags.noneSet(access, Opcodes.ACC_STATIC)) { + // Record all declared instance methods, including abstract, bridge, and native methods, as + // they all take precedence over default methods. + if (coreLibrarySupport != null) { + // Foreshadow any type renaming to avoid issues with double-desugaring (b/111447199) + desc = coreLibrarySupport.getRemapper().mapMethodDesc(desc); + } + instanceMethods.add(name + ":" + desc); + } + } + + private boolean recordedInstanceMethod(String name, String desc) { + if (coreLibrarySupport != null) { + // Foreshadow any type renaming to avoid issues with double-desugaring (b/111447199) + desc = coreLibrarySupport.getRemapper().mapMethodDesc(desc); + } + return instanceMethods.contains(name + ":" + desc); + } + + /** + * Visitor for interfaces that produces delegates in the class visited by the outer {@link + * DefaultMethodClassFixer} for every default method encountered. + */ + private class DefaultMethodStubber extends ClassVisitor { + + private final boolean isBootclasspathInterface; + private final boolean mayNeedStubsForSuperclass; + + private String stubbedInterfaceName; + + public DefaultMethodStubber( + boolean isBootclasspathInterface, boolean mayNeedStubsForSuperclass) { + super(Opcodes.ASM9); + this.isBootclasspathInterface = isBootclasspathInterface; + this.mayNeedStubsForSuperclass = mayNeedStubsForSuperclass; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE)); + checkState(stubbedInterfaceName == null); + stubbedInterfaceName = name; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (shouldStubAsDefaultMethod(access, name, desc)) { + // Remember we stubbed this method in case it's also defined by subsequently visited + // interfaces. javac would force the method to be defined explicitly if there any two + // definitions conflict, but see stubMissingDefaultMethods() for how we deal with default + // methods redefined in interfaces extending another. + recordIfInstanceMethod(access, name, desc); + String owner = InterfaceDesugaring.getCompanionClassName(stubbedInterfaceName); + if (!isBootclasspathInterface) { + // Don't record these dependencies, as we can't check them + depsCollector.assumeCompanionClass(internalName, owner); + } + + // Add this method to the class we're desugaring and stub in a body to call the default + // implementation in the interface's companion class. ijar omits these methods when setting + // ACC_SYNTHETIC modifier, so don't. + // Signatures can be wrong, e.g., when type variables are introduced, instantiated, or + // refined in the class we're processing, so drop them. + MethodVisitor stubMethod = + DefaultMethodClassFixer.this.visitMethod(access, name, desc, (String) null, exceptions); + + String receiverName = stubbedInterfaceName; + String calledMethodName = name + InterfaceDesugaring.DEFAULT_COMPANION_METHOD_SUFFIX; + if (mayNeedStubsForSuperclass) { + // Reflect what CoreLibraryInvocationRewriter would do if it encountered a super-call to a + // moved implementation of an emulated method. Equivalent to emitting the invokespecial + // super call here and relying on CoreLibraryInvocationRewriter for the rest + Class emulatedImplementation = + coreLibrarySupport.getCoreInterfaceRewritingTarget( + Opcodes.INVOKESPECIAL, superName, name, desc, /*itf=*/ false); + if (emulatedImplementation != null && !emulatedImplementation.isInterface()) { + receiverName = internalName(emulatedImplementation); + owner = checkNotNull(coreLibrarySupport.getMoveTarget(receiverName, name, desc)); + calledMethodName = name; + } + } + + int slot = 0; + stubMethod.visitVarInsn(Opcodes.ALOAD, slot++); // load the receiver + Type neededType = Type.getMethodType(desc); + for (Type arg : neededType.getArgumentTypes()) { + stubMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); + slot += arg.getSize(); + } + stubMethod.visitMethodInsn( + Opcodes.INVOKESTATIC, + owner, + calledMethodName, + InterfaceDesugaring.companionDefaultMethodDescriptor(receiverName, desc), + /*isInterface=*/ false); + stubMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); + + stubMethod.visitMaxs(0, 0); // rely on class writer to compute these + stubMethod.visitEnd(); + return null; // don't visit the visited interface's default method + } else if (shouldStubAsBridgeDefaultMethod(access, name, desc)) { + recordIfInstanceMethod(access, name, desc); + MethodVisitor stubMethod = + DefaultMethodClassFixer.this.visitMethod(access, name, desc, (String) null, exceptions); + // If we're visiting a bootclasspath interface then we most likely don't have the code. + // That means we can't just copy the method bodies as we're trying to do below. + if (isBootclasspathInterface) { + // Synthesize a "bridge" method that calls the true implementation + Method bridged = findBridgedMethod(name, desc); + checkState( + bridged != null, + "TODO: Can't stub core interface bridge method %s.%s %s in %s", + stubbedInterfaceName, + name, + desc, + internalName); + + int slot = 0; + stubMethod.visitVarInsn(Opcodes.ALOAD, slot++); // load the receiver + Type neededType = Type.getType(bridged); + Type[] neededArgTypes = neededType.getArgumentTypes(); + Type[] parameterTypes = Type.getArgumentTypes(desc); + for (int i = 0; i < neededArgTypes.length; ++i) { + Type arg = neededArgTypes[i]; + stubMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); + if (!arg.equals(parameterTypes[i])) { + checkState( + arg.getSort() == Type.ARRAY || arg.getSort() == Type.OBJECT, + "Can't cast parameter %s from in bridge for %s.%s%s to %s", + i, + stubbedInterfaceName, + name, + desc, + arg.getClassName()); + stubMethod.visitTypeInsn(Opcodes.CHECKCAST, arg.getInternalName()); + } + slot += arg.getSize(); + } + // Just call the bridged method directly on the visited class using invokevirtual + stubMethod.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + internalName, + name, + neededType.getDescriptor(), + /*isInterface=*/ false); + stubMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); + + stubMethod.visitMaxs(0, 0); // rely on class writer to compute these + stubMethod.visitEnd(); + return null; // don't visit the visited interface's bridge method + } else { + // For bridges we just copy their bodies instead of going through the companion class. + // Meanwhile, we also need to desugar the copied method bodies, so that any calls to + // interface methods are correctly handled. + return new InterfaceDesugaring.InterfaceInvocationRewriter( + stubMethod, + stubbedInterfaceName, + bootclasspath, + targetLoader, + depsCollector, + internalName); + } + } else { + return null; // not a default or bridge method or the class already defines this method. + } + } + + /** + * Returns a non-bridge interface method with given name that a method with the given descriptor + * can bridge to, if any such method can be found. + */ + @Nullable + private Method findBridgedMethod(String name, String desc) { + Type[] paramTypes = Type.getArgumentTypes(desc); + Type returnType = Type.getReturnType(desc); + Class itf = loadFromInternal(stubbedInterfaceName); + checkArgument(itf.isInterface(), "Should be an interface: %s", stubbedInterfaceName); + + // 1. Find the bridge method we're trying to implement + Method bridge = null; + for (Method m : itf.getDeclaredMethods()) { + if (m.isBridge() + && m.getName().equals(name) + && Arrays.equals(paramTypes, Type.getArgumentTypes(m)) + && returnType.equals(Type.getReturnType(m))) { + bridge = m; + break; + } + } + checkState(bridge != null, "Couldn't find bridge %s.%s %s", stubbedInterfaceName, name, desc); + checkState(bridge.getParameterCount() == paramTypes.length); + + // 2. Try to find the method being bridged + Method result = null; + next_method: + for (Method m : itf.getDeclaredMethods()) { + if (m.isBridge() || Modifier.isStatic(m.getModifiers())) { + continue; + } + if (!m.getName().equals(name)) { + continue; + } + + if (Arrays.equals(paramTypes, Type.getArgumentTypes(m))) { + // All argument types match, only return type will differ: this is the method we want + return m; + } else if (m.getParameterCount() == bridge.getParameterCount()) { + for (int i = 0; i < m.getParameterCount(); ++i) { + if (!bridge.getParameterTypes()[i].isAssignableFrom(m.getParameterTypes()[i])) { + continue next_method; + } + } + + // All of m's parameter types are subtypes of the bridge's parameter types (or primitives) + if (result == null) { + result = m; + } else { + // Bail if we find multiple methods that could be bridged + return null; + } + } + } + return result; + } + } + + /** + * Visitor for interfaces that recursively searches interfaces for default method declarations. + */ + private class DefaultMethodFinder extends ClassVisitor { + @SuppressWarnings("hiding") + private ImmutableList interfaces; + + private boolean found; + + public DefaultMethodFinder() { + super(Opcodes.ASM9); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE)); + checkState(this.interfaces == null); + this.interfaces = ImmutableList.copyOf(interfaces); + } + + public boolean foundDefaultMethods() { + return found; + } + + @Override + public void visitEnd() { + if (!found) { + found = defaultMethodsDefined(this.interfaces); + } + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (!found && shouldStubAsDefaultMethod(access, name, desc)) { + // Found a default method we're not ignoring (instanceMethods at this point contains methods + // the top-level visited class implements itself). + found = true; + } + return null; // we don't care about the actual code in these methods + } + } + + private class InstanceMethodRecorder extends ClassVisitor { + + private final boolean ignoreEmulatedMethods; + + private String className; + + public InstanceMethodRecorder(boolean ignoreEmulatedMethods) { + super(Opcodes.ASM9); + this.ignoreEmulatedMethods = ignoreEmulatedMethods; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkArgument(!BitFlags.isInterface(access)); + className = name; // updated every time we start visiting another superclass + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (ignoreEmulatedMethods + && !BitFlags.isStatic(access) // short-circuit + && coreLibrarySupport.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEVIRTUAL, className, name, desc, /*itf=*/ false) + != null) { + // *don't* record emulated core library method implementations in immediate subclasses of + // emulated core library clasess so that they can be stubbed (since the inherited + // implementation may be missing at runtime). + return null; + } + recordIfInstanceMethod(access, name, desc); + return null; + } + } + + /** + * Detector to determine whether an interface needs to be initialized when it is loaded. + * + *

If the interface has a default method, and its initializes any of its fields, then + * this interface needs to be initialized. + */ + private static class InterfaceInitializationNecessityDetector extends ClassVisitor { + + private final String internalName; + private boolean hasFieldInitializedInClinit; + private boolean hasDefaultMethods; + + public InterfaceInitializationNecessityDetector(String internalName) { + super(Opcodes.ASM9); + this.internalName = internalName; + } + + public boolean needsToInitialize() { + return hasDefaultMethods && hasFieldInitializedInClinit; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + checkState( + internalName.equals(name), + "Inconsistent internal names: expected=%s, real=%s", + internalName, + name); + checkArgument( + BitFlags.isSet(access, Opcodes.ACC_INTERFACE), + "This class visitor is only used for interfaces."); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (!hasDefaultMethods) { + hasDefaultMethods = isNonBridgeDefaultMethod(access); + } + if ("".equals(name)) { + return new MethodVisitor(Opcodes.ASM9) { + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (opcode == Opcodes.PUTSTATIC && internalName.equals(owner)) { + hasFieldInitializedInClinit = true; + } + } + }; + } + return null; // Do not care about the code. + } + } + + /** Method visitor that changes super constructor calls to a new base class. */ + private static class ConstructorFixer extends MethodVisitor { + + private final String newSuperName; + + /** + * This helps us find the very first constructor invocation we visit. We're looking for a + * sequence ALOAD_0 ; INVOKESPECIAL java.lang.Object() while avoiding "new Object()" which + * results in a NEW followed by the same INVOKESPECIAL for Object's constructor that we're + * looking for. Note that anonymous inner class constructors store captured variables into + * fields before calling super(), so we have to tolerate some unrelated logic, but since + * Object's constructor is parameterless we thankfully don't have to worry about handwritten + * code for constructor arguments. + */ + private boolean looking = true; + + ConstructorFixer(MethodVisitor dest, String newSuperName) { + super(Opcodes.ASM9, dest); + this.newSuperName = newSuperName; + } + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String descriptor, boolean isInterface) { + if (looking + && opcode == Opcodes.INVOKESPECIAL + && "".equals(name) + && "java/lang/Object".equals(owner)) { + // Call new base class's constructor instead of j.l.Object(). Note owner can be the + // visited class for this() constructor calls, and we don't need to touch those here. + owner = newSuperName; + } + // Since Object's constructor has no arguments it must be the very first INVOKE (because there + // can't be any preceding logic to set up constructor arguments), so stop looking regardless + // of whether we rewrote that INVOKE or not (but ignore inserted JaCoCo invocations). + if (looking && (opcode != Opcodes.INVOKESTATIC || !"$jacocoInit".equals(name))) { + looking = false; + } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + if (opcode == Opcodes.NEW && "java/lang/Object".equals(type)) { + // NEW java/lang/Object is followed by the very INVOKESPECIAL we're looking for, but we + // don't want to rewrite it. Since there can't be a super() call after this, just stop. + looking = false; + } + super.visitTypeInsn(opcode, type); + } + } + + /** Comparator for classes and interfaces that compares by whether subtyping relationship. */ + enum SubtypeComparator implements Comparator> { + /** Orders subtypes before supertypes and breaks ties lexicographically. */ + INSTANCE; + + @Override + public int compare(Class o1, Class o2) { + if (o1 == o2) { + return 0; + } + // order subtypes before supertypes + if (o1.isAssignableFrom(o2)) { // o1 is supertype of o2 + return 1; // we want o1 to come after o2 + } + if (o2.isAssignableFrom(o1)) { // o2 is supertype of o1 + return -1; // we want o2 to come after o1 + } + // o1 and o2 aren't comparable so arbitrarily impose lexicographical ordering + return o1.getName().compareTo(o2.getName()); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java new file mode 100644 index 00000000000000..0d531e0c46bcff --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java @@ -0,0 +1,1162 @@ +// 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.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.devtools.build.android.desugar.LambdaClassMaker.LAMBDA_METAFACTORY_DUMPER_PROPERTY; +import static com.google.devtools.build.android.desugar.retarget.ReplacementRange.DESUGAR_JAVA8_CORE_LIBS; +import static com.google.devtools.build.android.desugar.retarget.ReplacementRange.DESUGAR_JAVA8_LIBS; +import static com.google.devtools.build.android.desugar.retarget.ReplacementRange.REPLACE_CALLS_TO_LONG_UNSIGNED; +import static com.google.devtools.build.android.desugar.retarget.ReplacementRange.REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS; +import static com.google.devtools.build.android.desugar.strconcat.IndyStringConcatDesugaring.INVOKE_JDK11_STRING_CONCAT; + +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.collect.ConcurrentHashMultiset; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.flogger.GoogleLogger; +import com.google.common.io.ByteStreams; +import com.google.common.io.Closer; +import com.google.common.io.Resources; +import com.google.devtools.build.android.desugar.config.DesugarOptions; +import com.google.devtools.build.android.desugar.corelibadapter.InvocationSiteTransformationRecord; +import com.google.devtools.build.android.desugar.corelibadapter.InvocationSiteTransformationRecord.InvocationSiteTransformationRecordBuilder; +import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdaptersGenerator; +import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiInvocationSite; +import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiInvocationSite.ImmutableLabelRemover; +import com.google.devtools.build.android.desugar.covariantreturn.NioBufferRefConverter; +import com.google.devtools.build.android.desugar.io.BootClassPathDigest; +import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter; +import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter.UnprefixingClassWriter; +import com.google.devtools.build.android.desugar.io.FileBasedTypeReferenceClosure; +import com.google.devtools.build.android.desugar.io.FileContentProvider; +import com.google.devtools.build.android.desugar.io.HeaderClassLoader; +import com.google.devtools.build.android.desugar.io.IndexedInputs; +import com.google.devtools.build.android.desugar.io.InputFileProvider; +import com.google.devtools.build.android.desugar.io.JarDigest; +import com.google.devtools.build.android.desugar.io.OutputFileProvider; +import com.google.devtools.build.android.desugar.io.ResourceBasedClassFiles; +import com.google.devtools.build.android.desugar.io.ThrowingClassLoader; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberUseCounter; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.DesugarMethodAttribute; +import com.google.devtools.build.android.desugar.nest.NestAnalyzer; +import com.google.devtools.build.android.desugar.nest.NestDesugaring; +import com.google.devtools.build.android.desugar.nest.NestDigest; +import com.google.devtools.build.android.desugar.preanalysis.InputPreAnalyzer; +import com.google.devtools.build.android.desugar.retarget.ClassMemberRetargetConfig; +import com.google.devtools.build.android.desugar.retarget.ClassMemberRetargetRewriter; +import com.google.devtools.build.android.desugar.retarget.ReplacementRange; +import com.google.devtools.build.android.desugar.strconcat.IndyStringConcatDesugaring; +import com.google.devtools.build.android.desugar.typeannotation.LocalTypeAnnotationUse; +import com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchy; +import com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyScavenger; +import com.google.devtools.build.android.r8.DependencyCollector; +import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest; +import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +/** + * Command-line tool to desugar Java 8 constructs that dx doesn't know what to do with, in + * particular lambdas and method references. + */ +public class Desugar { + + // It is important that this method is called first. See its javadoc. + private static final Path DUMP_DIRECTORY = createAndRegisterLambdaDumpDirectory(); + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private static final ResourceBasedClassFiles resourceBasedClassFiles = + new ResourceBasedClassFiles(); + private static final String RUNTIME_LIB_PACKAGE = + "com/google/devtools/build/android/desugar/runtime/"; + private static final Attribute[] customAttributes = {new DesugarMethodAttribute()}; + private final DesugarOptions options; + private final CoreLibraryRewriter rewriter; + private final LambdaClassMaker lambdas; + private final GeneratedClassStore store = new GeneratedClassStore(); + private final ClassMemberUseCounter classMemberUseCounter = + new ClassMemberUseCounter(ConcurrentHashMultiset.create()); + private final Set visitedExceptionTypes = new LinkedHashSet<>(); + /** The counter to record the times of try-with-resources desugaring is invoked. */ + private final AtomicInteger numOfTryWithResourcesInvoked = new AtomicInteger(); + + private final boolean outputJava7; + private final boolean allowDefaultMethods; + private final boolean allowTryWithResources; + private final boolean allowCallsToObjectsNonNull; + private final boolean allowCallsToLongCompare; + + private final ImmutableSet enabledInvocationReplacementRanges; + /** An instance of Desugar is expected to be used ONLY ONCE */ + private boolean used; + + @Nullable private TypeHierarchy typeHierarchy; + @Nullable private BootClassPathDigest bootClassPathDigest; + + private Desugar(DesugarOptions options, Path dumpDirectory) { + this.options = options; + this.rewriter = new CoreLibraryRewriter(options.coreLibrary ? ClassName.IN_PROCESS_LABEL : ""); + this.lambdas = new LambdaClassMaker(dumpDirectory); + this.outputJava7 = options.minSdkVersion < 24; + this.allowDefaultMethods = + options.desugarInterfaceMethodBodiesIfNeeded || options.minSdkVersion >= 24; + this.allowTryWithResources = + !options.desugarTryWithResourcesIfNeeded || options.minSdkVersion >= 19; + + this.allowCallsToObjectsNonNull = options.minSdkVersion >= 19; + this.allowCallsToLongCompare = options.minSdkVersion >= 19 && !options.alwaysRewriteLongCompare; + boolean allowCallsToLongUnsigned = options.minSdkVersion >= 26; + boolean allowCallsToPrimitiveWrappers = options.minSdkVersion >= 24; + + ImmutableSet.Builder invocationReplacementRangesBuilder = + ImmutableSet.builder(); + + // Exclude the dependency on desugar runtime libs from desugar_jdk_libs. + if (!allowCallsToLongUnsigned && !this.options.coreLibrary) { + invocationReplacementRangesBuilder.add(REPLACE_CALLS_TO_LONG_UNSIGNED); + } + // Exclude the dependency on desugar runtime libs from desugar_jdk_libs. + if (!allowCallsToPrimitiveWrappers && !this.options.coreLibrary) { + invocationReplacementRangesBuilder.add(REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS); + } + + if (options.desugarCoreLibs && options.autoDesugarShadowedApiUse) { + invocationReplacementRangesBuilder.add(DESUGAR_JAVA8_LIBS); + } + + if (options.coreLibrary) { + invocationReplacementRangesBuilder.add(DESUGAR_JAVA8_CORE_LIBS); + } + + enabledInvocationReplacementRanges = invocationReplacementRangesBuilder.build(); + this.used = false; + } + + private void desugar() throws Exception { + checkState(!this.used, "This Desugar instance has been used. Please create another one."); + this.used = true; + + List platformJars = + options.classpath.stream() + .filter(path -> path.toString().endsWith(".jar")) + .filter(path -> JarDigest.fromPath(path).isPlatformJar()) + .collect(Collectors.toList()); + if (!platformJars.isEmpty()) { + if (options.verbose) { + logger.atInfo().log( + "Platform Jars in class path added to boot class path: %s", platformJars); + } + options.bootclasspath = + ImmutableList.builder().addAll(options.bootclasspath).addAll(platformJars).build(); + } + + if (options.autoDesugarShadowedApiUse) { + typeHierarchy = + TypeHierarchyScavenger.analyze( + ImmutableList.builder() + .addAll(options.inputJars) + .addAll(options.classpath) + .addAll(options.bootclasspath) + .build(), + /* requireTypeResolutionComplete= */ false); + bootClassPathDigest = BootClassPathDigest.create(ImmutableList.copyOf(options.bootclasspath)); + + if (options.verbose) { + ImmutableList shadowedTypes = + typeHierarchy.methodMetadata().values().stream() + .filter(method -> method.owner().isAndroidDomainType()) + .flatMap(methodDeclInfo -> methodDeclInfo.headerTypeNameSet().stream()) + .filter(ClassName::isDesugarShadowedType) + .distinct() + .sorted() + .collect(toImmutableList()); + logger.atInfo().log( + "---> Total number of boot class entries(%d) from packages: %s, from jars %s on input" + + " %s.", + bootClassPathDigest.resourceEntrySize(), + bootClassPathDigest.listPackageLeadingPrefixes(), + bootClassPathDigest, + options.inputJars); + logger.atInfo().log( + "\n%s\n", shadowedTypes.size(), shadowedTypes); + } + } + + ClassMemberRetargetConfig classMemberRetargetConfig = + ClassMemberRetargetConfig.builder() + .addInvocationReplacementConfigUrl(ClassMemberRetargetConfig.DEFAULT_PROTO_URL) + .addAllEnabledInvocationReplacementRange(enabledInvocationReplacementRanges) + .build(); + + try (Closer closer = Closer.create()) { + IndexedInputs indexedBootclasspath = + new IndexedInputs(toRegisteredInputFileProvider(closer, options.bootclasspath)); + // Use a classloader that as much as possible uses the provided bootclasspath instead of + // the tool's system classloader. Unfortunately we can't do that for java. classes. + ClassLoader bootclassloader = + options.bootclasspath.isEmpty() + ? new ThrowingClassLoader() + : new HeaderClassLoader(indexedBootclasspath, rewriter, new ThrowingClassLoader()); + IndexedInputs indexedClasspath = + new IndexedInputs(toRegisteredInputFileProvider(closer, options.classpath)); + // Process each input separately + for (InputOutputPair inputOutputPair : toInputOutputPairs(options)) { + desugarOneInput( + inputOutputPair, + indexedClasspath, + bootclassloader, + new ClassReaderFactory(indexedBootclasspath, rewriter), + bootClassPathDigest, + classMemberRetargetConfig); + } + } + } + + private void desugarOneInput( + InputOutputPair inputOutputPair, + IndexedInputs indexedClasspath, + ClassLoader bootclassloader, + ClassReaderFactory bootclasspathReader, + BootClassPathDigest bootClassPathDigest, + ClassMemberRetargetConfig classMemberRetargetConfig) + throws Exception { + Path inputPath = inputOutputPair.getInput(); // the jar + Path outputPath = inputOutputPair.getOutput(); + checkArgument( + Files.isDirectory(inputPath) || !Files.isDirectory(outputPath), + "Input jar file requires an output jar file"); + + try (OutputFileProvider outputFileProvider = OutputFileProvider.create(outputPath); + InputFileProvider inputFiles = InputFileProvider.open(inputPath)) { + DependencyCollector depsCollector = createDepsCollector(); + IndexedInputs indexedInputFiles = new IndexedInputs(ImmutableList.of(inputFiles)); + // Prepend classpath with input file itself so LambdaDesugaring can load classes with + // lambdas. + IndexedInputs indexedClasspathAndInputFiles = indexedClasspath.withParent(indexedInputFiles); + // Note that input file and classpath need to be in the same classloader because + // we typically get the header Jar for inputJar on the classpath and having the header + // Jar in a parent loader means the header version is preferred over the real thing. + ClassLoader loader = + new HeaderClassLoader(indexedClasspathAndInputFiles, rewriter, bootclassloader); + + ClassReaderFactory classpathReader = null; + ClassReaderFactory bridgeMethodReader = null; + if (outputJava7) { + classpathReader = new ClassReaderFactory(indexedClasspathAndInputFiles, rewriter); + if (options.copyBridgesFromClasspath) { + bridgeMethodReader = classpathReader; + } else { + bridgeMethodReader = new ClassReaderFactory(indexedInputFiles, rewriter); + } + } + + ImmutableSet.Builder interfaceLambdaMethodCollector = ImmutableSet.builder(); + ClassVsInterface interfaceCache = new ClassVsInterface(classpathReader); + final CoreLibrarySupport coreLibrarySupport = + options.desugarCoreLibs + ? new CoreLibrarySupport( + rewriter, + loader, + options.rewriteCoreLibraryPrefixes, + options.emulateCoreLibraryInterfaces, + options.dontTouchCoreLibraryMembers, + classMemberRetargetConfig) + : null; + + InvocationSiteTransformationRecordBuilder callSiteTransCollector = + InvocationSiteTransformationRecord.builder(); + ImmutableSet.Builder requiredRuntimeSupportTypes = ImmutableSet.builder(); + + InputPreAnalyzer inputPreAnalyzer = + new InputPreAnalyzer(inputFiles.toInputFileStreams(), customAttributes); + + inputPreAnalyzer.process(); + ClassAttributeRecord classAttributeRecord = inputPreAnalyzer.getClassAttributeRecord(); + ClassMemberRecord classMemberRecord = inputPreAnalyzer.getClassMemberRecord(); + + // Apply core library type name remapping to the digest instance produced by the nest + // analyzer, since the analysis-oriented nest analyzer visits core library classes without + // name remapping as those transformation-oriented visitors. + NestDigest nestDigest = + NestAnalyzer.digest(classAttributeRecord, classMemberRecord) + .acceptTypeMapper(rewriter.getPrefixer()); + + desugarClassesInInput( + inputFiles, + outputFileProvider, + loader, + classpathReader, + depsCollector, + bootclasspathReader, + coreLibrarySupport, + interfaceCache, + interfaceLambdaMethodCollector, + callSiteTransCollector, + bootClassPathDigest, + classAttributeRecord, + nestDigest, + requiredRuntimeSupportTypes, + classMemberRetargetConfig); + + desugarAndWriteDumpedLambdaClassesToOutput( + outputFileProvider, + loader, + classpathReader, + depsCollector, + bootclasspathReader, + coreLibrarySupport, + interfaceCache, + interfaceLambdaMethodCollector.build(), + bridgeMethodReader, + callSiteTransCollector, + bootClassPathDigest, + classAttributeRecord, + requiredRuntimeSupportTypes, + classMemberRetargetConfig); + + desugarAndWriteGeneratedClasses( + outputFileProvider, + loader, + classpathReader, + depsCollector, + bootclasspathReader, + coreLibrarySupport, + callSiteTransCollector, + bootClassPathDigest, + classAttributeRecord, + requiredRuntimeSupportTypes, + classMemberRetargetConfig); + + copyRuntimeClasses( + outputFileProvider, coreLibrarySupport, requiredRuntimeSupportTypes.build()); + + ShadowedApiAdaptersGenerator adaptersGenerator = + ShadowedApiAdaptersGenerator.create(callSiteTransCollector.build()); + adaptersGenerator.getApiAdapters().sink(outputFileProvider); + copyTypeConverterClasses( + outputFileProvider, ImmutableSet.copyOf(adaptersGenerator.getTypeConverters())); + + byte[] depsInfo = depsCollector.toByteArray(); + if (depsInfo != null) { + outputFileProvider.write(OutputFileProvider.DESUGAR_DEPS_FILENAME, depsInfo); + } + } + + ImmutableMap lambdasLeftBehind = lambdas.drain(); + checkState(lambdasLeftBehind.isEmpty(), "Didn't process %s", lambdasLeftBehind); + ImmutableMap generatedLeftBehind = store.drain(); + checkState(generatedLeftBehind.isEmpty(), "Didn't process %s", generatedLeftBehind.keySet()); + } + + private static void copyTypeConverterClasses( + OutputFileProvider outputFileProvider, ImmutableSet initialTypes) { + FileBasedTypeReferenceClosure typeReferenceClosure = + new FileBasedTypeReferenceClosure( + ClassName::isInDesugarRuntimeLibrary, resourceBasedClassFiles); + typeReferenceClosure.findReachableReferencedTypes(initialTypes).stream() + .map(resourceBasedClassFiles::getContent) + .forEach(fileContent -> fileContent.sink(outputFileProvider)); + } + + /** + * Returns a dependency collector for use with a single input Jar. If {@link + * DesugarOptions#emitDependencyMetadata} is set, this method instantiates the collector + * reflectively to allow compiling and using the desugar tool without this mechanism. + */ + private DependencyCollector createDepsCollector() { + if (options.emitDependencyMetadata) { + try { + return (DependencyCollector) + Thread.currentThread() + .getContextClassLoader() + .loadClass( + "com.google.devtools.build.android.desugar.dependencies.MetadataCollector") + .getConstructor(Boolean.TYPE) + .newInstance(options.tolerateMissingDependencies); + } catch (ReflectiveOperationException | SecurityException e) { + throw new IllegalStateException("Can't emit desugaring metadata as requested", e); + } + } else if (options.tolerateMissingDependencies) { + return DependencyCollector.NoWriteCollectors.NOOP; + } else { + return DependencyCollector.NoWriteCollectors.FAIL_ON_MISSING; + } + } + + private void copyRuntimeClasses( + OutputFileProvider outputFileProvider, + @Nullable CoreLibrarySupport coreLibrarySupport, + ImmutableSet requiredRuntimeSupportTypes) { + // 1. Copy any runtime classes needed due to core library desugaring. + if (coreLibrarySupport != null) { + coreLibrarySupport.usedRuntimeHelpers().stream() + .filter(className -> className.startsWith(RUNTIME_LIB_PACKAGE)) + .distinct() + .forEach( + className -> { + // We want core libraries to remain self-contained, so fail if we get here. + checkState(!options.coreLibrary, "Core library shouldn't depend on %s", className); + try (InputStream stream = + Desugar.class.getClassLoader().getResourceAsStream(className + ".class")) { + outputFileProvider.write( + className + ".class", + ByteStreams.toByteArray( + checkNotNull(stream, "Resource Not Found for %s.", className))); + } catch (IOException e) { + throw new IOError(e); + } + }); + } + + // 2. Write required types in runtime library to output. + requiredRuntimeSupportTypes.forEach( + type -> resourceBasedClassFiles.getContent(type).sink(outputFileProvider)); + + // 3. See if we need to copy StringConcats methods for Indify string desugaring. + if (!options.coreLibrary + && classMemberUseCounter.getMemberUseCount(INVOKE_JDK11_STRING_CONCAT) > 0) { + String resourceName = "com/google/devtools/build/android/desugar/runtime/StringConcats.class"; + try (InputStream stream = Resources.getResource(resourceName).openStream()) { + outputFileProvider.write(resourceName, ByteStreams.toByteArray(stream)); + } catch (IOException e) { + throw new IOError(e); + } + } + + // 4. See if we need to copy try-with-resources runtime library + if (allowTryWithResources || options.desugarTryWithResourcesOmitRuntimeClasses) { + // try-with-resources statements are okay in the output jar. + return; + } + if (numOfTryWithResourcesInvoked.get() <= 0) { + // the try-with-resources desugaring pass does nothing, so no need to copy these class files. + return; + } + for (String className : + TryWithResourcesRewriter.THROWABLE_EXT_CLASS_INTERNAL_NAMES_WITH_CLASS_EXT) { + try (InputStream stream = Desugar.class.getClassLoader().getResourceAsStream(className)) { + outputFileProvider.write(className, ByteStreams.toByteArray(stream)); + } catch (IOException e) { + throw new IOError(e); + } + } + } + + /** Desugar the classes that are in the inputs specified in the command line arguments. */ + private void desugarClassesInInput( + InputFileProvider inputFiles, + OutputFileProvider outputFileProvider, + ClassLoader loader, + @Nullable ClassReaderFactory classpathReader, + DependencyCollector depsCollector, + ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport, + ClassVsInterface interfaceCache, + ImmutableSet.Builder interfaceLambdaMethodCollector, + InvocationSiteTransformationRecordBuilder callSiteRecord, + BootClassPathDigest bootClassPathDigest, + ClassAttributeRecord classAttributeRecord, + NestDigest nestDigest, + ImmutableSet.Builder requiredRuntimeSupportTypes, + ClassMemberRetargetConfig classMemberRetargetConfig) + throws IOException { + + for (FileContentProvider inputFileProvider : + Iterables.concat(inputFiles.toInputFileStreams(), nestDigest.getCompanionFileProviders())) { + String inputFilename = inputFileProvider.getBinaryPathName(); + if ("module-info.class".equals(inputFilename) + || inputFilename.startsWith("META-INF/versions/")) { + continue; // drop module-info.class and META-INF/versions/ since d8 will drop them anyway + } + if (OutputFileProvider.DESUGAR_DEPS_FILENAME.equals(inputFilename)) { + // TODO(kmb): rule out that this happens or merge input file with what's in depsCollector + continue; // skip as we're writing a new file like this at the end or don't want it + } + + try (InputStream content = inputFileProvider.get()) { + // We can write classes uncompressed since they need to be converted to .dex format + // for Android anyways. Resources are written as they were in the input jar to avoid + // any danger of accidentally uncompressed resources ending up in an .apk. We also simply + // copy classes from Desugar's runtime library, which we build so they need no desugaring. + // The runtime library typically uses constructs we'd otherwise desugar, so it's easier + // to just skip it should it appear as a regular input (for idempotency). + if (inputFilename.endsWith(".class") + && ClassName.fromClassFileName(inputFilename).isDesugarEligible()) { + ClassReader reader = rewriter.reader(content); + UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS); + ClassVisitor visitor = + createClassVisitorsForClassesInInputs( + loader, + classpathReader, + depsCollector, + bootclasspathReader, + coreLibrarySupport, + interfaceCache, + interfaceLambdaMethodCollector, + writer, + reader, + nestDigest, + callSiteRecord, + bootClassPathDigest, + classAttributeRecord, + requiredRuntimeSupportTypes, + classMemberRetargetConfig); + if (writer == visitor) { + // Just copy the input if there are no rewritings + outputFileProvider.write(inputFilename, reader.b); + } else { + reader.accept(visitor, customAttributes, ClassReader.EXPAND_FRAMES); + String filename = writer.getClassName() + ".class"; + checkState( + (options.coreLibrary && coreLibrarySupport != null) + || filename.equals(inputFilename)); + outputFileProvider.write(filename, writer.toByteArray()); + } + } else { + // Most other files (and directories) we want to just copy, but... + String outputFilename = inputFilename; + if (options.coreLibrary && coreLibrarySupport != null && inputFilename.endsWith("/")) { + // rename core library directories together with files in them + outputFilename = coreLibrarySupport.renameCoreLibrary(inputFilename); + } else if (coreLibrarySupport != null + && !inputFilename.endsWith("/") + && inputFilename.startsWith("META-INF/services/")) { + // rename j.u.ServiceLoader files for renamed core libraries so they're found + String serviceName = inputFilename.substring("META-INF/services/".length()); + if (!serviceName.contains("/") + && coreLibrarySupport.isRenamedCoreLibrary(serviceName.replace('.', '/'))) { + outputFilename = + "META-INF/services/" + + coreLibrarySupport + .renameCoreLibrary(serviceName.replace('.', '/')) + .replace('/', '.'); + } + } + outputFileProvider.copyFrom(inputFilename, inputFiles, outputFilename); + } + } + } + } + + /** + * Desugar the classes that are generated on the fly when we are desugaring the classes in the + * specified inputs. + */ + private void desugarAndWriteDumpedLambdaClassesToOutput( + OutputFileProvider outputFileProvider, + ClassLoader loader, + @Nullable ClassReaderFactory classpathReader, + DependencyCollector depsCollector, + ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport, + ClassVsInterface interfaceCache, + ImmutableSet interfaceLambdaMethods, + @Nullable ClassReaderFactory bridgeMethodReader, + InvocationSiteTransformationRecordBuilder callSiteTransCollector, + BootClassPathDigest bootClassPathDigest, + ClassAttributeRecord classAttributeRecord, + ImmutableSet.Builder requiredRuntimeSupportTypes, + ClassMemberRetargetConfig classMemberRetargetConfig) + throws IOException { + checkState( + !allowDefaultMethods || interfaceLambdaMethods.isEmpty(), + "Desugaring with default methods enabled moved interface lambdas"); + + // Write out the lambda classes we generated along the way + ImmutableMap lambdaClasses = lambdas.drain(); + checkState( + !options.onlyDesugarJavac9ForLint || lambdaClasses.isEmpty(), + "There should be no lambda classes generated: %s", + lambdaClasses.keySet()); + + for (Map.Entry lambdaClass : lambdaClasses.entrySet()) { + try (InputStream bytecode = Files.newInputStream(lambdaClass.getKey())) { + ClassReader reader = rewriter.reader(bytecode); + InvokeDynamicLambdaMethodCollector collector = new InvokeDynamicLambdaMethodCollector(); + reader.accept( + collector, customAttributes, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + ImmutableSet lambdaMethods = collector.getLambdaMethodsUsedInInvokeDynamics(); + checkState( + lambdaMethods.isEmpty(), + "Didn't expect to find lambda methods but found %s", + lambdaMethods); + UnprefixingClassWriter writer = + rewriter.writer(ClassWriter.COMPUTE_MAXS /*for invoking bridges*/); + ClassVisitor visitor = + createClassVisitorsForDumpedLambdaClasses( + loader, + classpathReader, + depsCollector, + bootclasspathReader, + coreLibrarySupport, + interfaceCache, + interfaceLambdaMethods, + bridgeMethodReader, + lambdaClass.getValue(), + writer, + reader, + callSiteTransCollector, + bootClassPathDigest, + classAttributeRecord, + requiredRuntimeSupportTypes, + classMemberRetargetConfig); + reader.accept(visitor, customAttributes, ClassReader.EXPAND_FRAMES); + checkState( + (options.coreLibrary && coreLibrarySupport != null) + || rewriter + .unprefix(lambdaClass.getValue().desiredInternalName()) + .equals(writer.getClassName())); + outputFileProvider.write(writer.getClassName() + ".class", writer.toByteArray()); + } + } + } + + private void desugarAndWriteGeneratedClasses( + OutputFileProvider outputFileProvider, + ClassLoader loader, + @Nullable ClassReaderFactory classpathReader, + DependencyCollector depsCollector, + ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport, + InvocationSiteTransformationRecordBuilder callSiteTransCollector, + BootClassPathDigest bootClassPathDigest, + ClassAttributeRecord classAttributeRecord, + ImmutableSet.Builder requiredRuntimeSupportTypes, + ClassMemberRetargetConfig classMemberRetargetConfig) + throws IOException { + // Write out any classes we generated along the way + if (coreLibrarySupport != null) { + coreLibrarySupport.makeDispatchHelpers(store); + } + ImmutableMap generatedClasses = store.drain(); + checkState( + generatedClasses.isEmpty() || (allowDefaultMethods && outputJava7), + "Didn't expect generated classes but got %s", + generatedClasses.keySet()); + for (Map.Entry generated : generatedClasses.entrySet()) { + UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS); + // checkState above implies that we want Java 7 .class files, so send through that visitor. + // Don't need a ClassReaderFactory b/c static interface methods should've been moved. + ClassVisitor visitor = writer; + visitor = + createTypeBasedClassVisitorsForClassesInInputs( + loader, + coreLibrarySupport, + visitor, + callSiteTransCollector, + bootClassPathDigest, + classAttributeRecord, + closeResourceMethodScanner -> generated.getValue().accept(closeResourceMethodScanner), + requiredRuntimeSupportTypes, + classMemberRetargetConfig); + + visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null, bootclasspathReader); + if (options.generateBaseClassesForDefaultMethods) { + // Use DefaultMethodClassFixer to make generated base classes extend other base classes if + // possible and add any stubs from extended interfaces + visitor = + new DefaultMethodClassFixer( + visitor, + /*useGeneratedBaseClasses=*/ true, + classpathReader, + depsCollector, + coreLibrarySupport, + bootclasspathReader, + loader); + } + generated.getValue().accept(visitor); + checkState( + (options.coreLibrary && coreLibrarySupport != null) + || rewriter.unprefix(generated.getKey()).equals(writer.getClassName())); + outputFileProvider.write(writer.getClassName() + ".class", writer.toByteArray()); + } + } + + /** + * Create the class visitors for the lambda classes that are generated on the fly. If no new class + * visitors are not generated, then the passed-in {@code writer} will be returned. + */ + private ClassVisitor createClassVisitorsForDumpedLambdaClasses( + ClassLoader loader, + @Nullable ClassReaderFactory classpathReader, + DependencyCollector depsCollector, + ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport, + ClassVsInterface interfaceCache, + ImmutableSet interfaceLambdaMethods, + @Nullable ClassReaderFactory bridgeMethodReader, + LambdaInfo lambdaClass, + UnprefixingClassWriter writer, + ClassReader input, + InvocationSiteTransformationRecordBuilder callSiteRecord, + BootClassPathDigest bootClassPathDigest, + ClassAttributeRecord classAttributeRecord, + ImmutableSet.Builder requiredRuntimeSupportTypes, + ClassMemberRetargetConfig classMemberRetargetConfig) { + ClassVisitor visitor = checkNotNull(writer); + + visitor = + createTypeBasedClassVisitorsForClassesInInputs( + loader, + coreLibrarySupport, + visitor, + callSiteRecord, + bootClassPathDigest, + classAttributeRecord, + closeResourceMethodScanner -> + input.accept(closeResourceMethodScanner, customAttributes, ClassReader.SKIP_DEBUG), + requiredRuntimeSupportTypes, + classMemberRetargetConfig); + + if (outputJava7) { + // null ClassReaderFactory b/c we don't expect to need it for lambda classes + visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null, bootclasspathReader); + if (options.desugarInterfaceMethodBodiesIfNeeded) { + visitor = + new DefaultMethodClassFixer( + visitor, + options.generateBaseClassesForDefaultMethods, + classpathReader, + depsCollector, + coreLibrarySupport, + bootclasspathReader, + loader); + visitor = + new InterfaceDesugaring( + visitor, + options.generateBaseClassesForDefaultMethods, + interfaceCache, + depsCollector, + coreLibrarySupport, + bootclasspathReader, + loader, + store, + options.legacyJacocoFix); + } + } + + visitor = + new LambdaClassFixer( + visitor, + lambdaClass, + bridgeMethodReader, + loader, + interfaceLambdaMethods, + allowDefaultMethods, + outputJava7); + // Send lambda classes through desugaring to make sure there's no invokedynamic + // instructions in generated lambda classes (checkState below will fail) + visitor = + new LambdaDesugaring( + visitor, + loader, + lambdas, + null, + ImmutableSet.of(), + classAttributeRecord, + allowDefaultMethods); + return visitor; + } + + /** + * Create the class visitors for the classes which are in the inputs. If new visitors are created, + * then all these visitors and the passed-in writer will be chained together. If no new visitor is + * created, then the passed-in {@code writer} will be returned. + */ + private ClassVisitor createClassVisitorsForClassesInInputs( + ClassLoader loader, + @Nullable ClassReaderFactory classpathReader, + DependencyCollector depsCollector, + ClassReaderFactory bootclasspathReader, + @Nullable CoreLibrarySupport coreLibrarySupport, + ClassVsInterface interfaceCache, + ImmutableSet.Builder interfaceLambdaMethodCollector, + UnprefixingClassWriter writer, + ClassReader input, + NestDigest nestDigest, + InvocationSiteTransformationRecordBuilder callSiteRecord, + BootClassPathDigest bootClassPathDigest, + ClassAttributeRecord classAttributeRecord, + ImmutableSet.Builder requiredRuntimeSupportTypes, + ClassMemberRetargetConfig classMemberRetargetConfig) { + ClassVisitor visitor = checkNotNull(writer); + + visitor = + createTypeBasedClassVisitorsForClassesInInputs( + loader, + coreLibrarySupport, + visitor, + callSiteRecord, + bootClassPathDigest, + classAttributeRecord, + closeResourceMethodScanner -> + input.accept(closeResourceMethodScanner, customAttributes, ClassReader.SKIP_DEBUG), + requiredRuntimeSupportTypes, + classMemberRetargetConfig); + + if (!options.onlyDesugarJavac9ForLint) { + if (outputJava7) { + visitor = new Java7Compatibility(visitor, classpathReader, bootclasspathReader); + if (options.desugarInterfaceMethodBodiesIfNeeded) { + visitor = + new DefaultMethodClassFixer( + visitor, + options.generateBaseClassesForDefaultMethods, + classpathReader, + depsCollector, + coreLibrarySupport, + bootclasspathReader, + loader); + visitor = + new InterfaceDesugaring( + visitor, + options.generateBaseClassesForDefaultMethods, + interfaceCache, + depsCollector, + coreLibrarySupport, + bootclasspathReader, + loader, + store, + options.legacyJacocoFix); + } + } + + // LambdaDesugaring is relatively expensive, so check first whether we need it. Additionally, + // we need to collect lambda methods referenced by invokedynamic instructions up-front anyway. + // TODO(kmb): Scan constant pool instead of visiting the class to find bootstrap methods etc. + InvokeDynamicLambdaMethodCollector collector = new InvokeDynamicLambdaMethodCollector(); + input.accept(collector, customAttributes, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + ImmutableSet methodsUsedInInvokeDynamics = + collector.getLambdaMethodsUsedInInvokeDynamics(); + if (!methodsUsedInInvokeDynamics.isEmpty() || collector.needOuterClassRewrite()) { + visitor = + new LambdaDesugaring( + visitor, + loader, + lambdas, + interfaceLambdaMethodCollector, + methodsUsedInInvokeDynamics, + classAttributeRecord, + allowDefaultMethods); + } + } + + if (options.desugarNestBasedPrivateAccess) { + visitor = new NestDesugaring(visitor, nestDigest); + } + + if (options.desugarIndifyStringConcat) { + visitor = new IndyStringConcatDesugaring(classMemberUseCounter, visitor, options.coreLibrary); + } + + visitor = new LocalTypeAnnotationUse(visitor); + + return visitor; + } + + /** + * Create a series of class visitors which support types in later JDK core libraries on early Java + * platforms. + */ + private ClassVisitor createTypeBasedClassVisitorsForClassesInInputs( + ClassLoader loader, + @Nullable CoreLibrarySupport coreLibrarySupport, + ClassVisitor baseClassVisitor, + InvocationSiteTransformationRecordBuilder callSiteRecord, + BootClassPathDigest bootClassPathDigest, + ClassAttributeRecord classAttributeRecord, + Consumer closeResourceMethodScannerConsumer, + ImmutableSet.Builder requiredRuntimeSupportTypes, + ClassMemberRetargetConfig classMemberRetargetConfig) { + ClassVisitor visitor = baseClassVisitor; + + if (coreLibrarySupport != null) { + visitor = new ImmutableLabelRemover(visitor); + visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport); + visitor = new CorePackageRenamer(visitor, coreLibrarySupport); + visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport); + if (options.autoDesugarShadowedApiUse) { + visitor = + new ShadowedApiInvocationSite( + visitor, callSiteRecord, bootClassPathDigest, classAttributeRecord, typeHierarchy); + } + } + + if (!allowTryWithResources) { + CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner(); + closeResourceMethodScannerConsumer.accept(closeResourceMethodScanner); + visitor = + new TryWithResourcesRewriter( + visitor, + loader, + visitedExceptionTypes, + numOfTryWithResourcesInvoked, + closeResourceMethodScanner.hasCloseResourceMethod()); + } + + if (!allowCallsToObjectsNonNull) { + visitor = new ObjectsRequireNonNullMethodRewriter(visitor, rewriter); + } + + if (!allowCallsToLongCompare) { + visitor = new LongCompareMethodRewriter(visitor, rewriter); + } + + visitor = + new ClassMemberRetargetRewriter( + visitor, classMemberRetargetConfig, requiredRuntimeSupportTypes); + + visitor = NioBufferRefConverter.create(visitor, rewriter.getPrefixer()); + + return visitor; + } + + public static void main(String[] args) throws Exception { + verifyLambdaDumpDirectoryRegistered(DUMP_DIRECTORY); + + // In persistent worker mode, Bazel sends the list of arguments + // both over argv to the Java process, and over stdin as a serialized + // proto. We check that it's running in the persistent worker mode + // by checking the first arg of the list, and parse it into DesugarOptions + // from the proto in runPersistentWorker later. + if (args.length > 0 && "--persistent_worker".equals(args[0])) { + runPersistentWorker(DUMP_DIRECTORY); + } else { + // If not, parse it regularly from argv. + DesugarOptions options = DesugarOptions.parseCommandLineOptions(args); + processRequest(options, DUMP_DIRECTORY); + } + } + + private static void runPersistentWorker(Path dumpDirectory) throws Exception { + while (true) { + WorkRequest request = WorkRequest.parseDelimitedFrom(System.in); + if (request == null) { + break; + } + + String[] argList = new String[request.getArgumentsCount()]; + argList = request.getArgumentsList().toArray(argList); + + DesugarOptions options = DesugarOptions.parseCommandLineOptions(argList); + + WorkResponse wr; + try { + processRequest(options, dumpDirectory); + wr = WorkResponse.newBuilder().setExitCode(0).build(); + logger.atInfo().log("Processing Request success: %s", wr); + } catch (Exception e) { + wr = + WorkResponse.newBuilder() + .setExitCode(1) + .setOutput(Throwables.getStackTraceAsString(e)) + .build(); + logger.atWarning().withCause(e).log("Processing Request exception: %s", wr); + } + + // We are in persistent worker mode, so send the persistent worker response back to Bazel + // through stdout. Without this, Bazel will timeout while waiting for the worker's response. + wr.writeDelimitedTo(System.out); + } + } + + private static void processRequest(DesugarOptions options, Path dumpDirectory) throws Exception { + checkArgument(!options.inputJars.isEmpty(), "--input is required"); + checkArgument( + options.inputJars.size() == options.outputJars.size(), + "Desugar requires the same number of inputs and outputs to pair them. #input=%s,#output=%s", + options.inputJars.size(), + options.outputJars.size()); + checkArgument( + !options.bootclasspath.isEmpty() || options.allowEmptyBootclasspath, + "At least one --bootclasspath_entry is required"); + for (Path path : options.bootclasspath) { + checkArgument(!Files.isDirectory(path), "Bootclasspath entry must be a jar file: %s", path); + } + checkArgument( + !options.desugarCoreLibs + || !options.rewriteCoreLibraryPrefixes.isEmpty() + || !options.emulateCoreLibraryInterfaces.isEmpty(), + "--desugar_supported_core_libs requires specifying renamed and/or emulated core libraries"); + + if (options.verbose) { + logger.atInfo().log("Lambda classes will be written under %s%n", dumpDirectory); + } + new Desugar(options, dumpDirectory).desugar(); + } + + static void verifyLambdaDumpDirectoryRegistered(Path dumpDirectory) throws IOException { + try { + Class klass = Class.forName("java.lang.invoke.InnerClassLambdaMetafactory"); + Field dumperField = klass.getDeclaredField("dumper"); + dumperField.setAccessible(true); + Object dumperValue = dumperField.get(null); + checkNotNull(dumperValue, "Failed to register lambda dump directory '%s'", dumpDirectory); + + Field dumperPathField = dumperValue.getClass().getDeclaredField("dumpDir"); + dumperPathField.setAccessible(true); + Object dumperPath = dumperPathField.get(dumperValue); + checkState( + dumperPath instanceof Path && Files.isSameFile(dumpDirectory, (Path) dumperPath), + "Inconsistent lambda dump directories. real='%s', expected='%s'", + dumperPath, + dumpDirectory); + } catch (ReflectiveOperationException e) { + // We do not want to crash Desugar, if we cannot load or access these classes or fields. + // We aim to provide better diagnostics. If we cannot, just let it go. + logger.atWarning().withCause(e).log( + "Failed to verify lambda dump directory due to unavailable class/field access. \n" + + "Continue desugaring..."); + } + } + + /** + * LambdaClassMaker generates lambda classes for us, but it does so by essentially simulating the + * call to LambdaMetafactory that the JVM would make when encountering an invokedynamic. + * LambdaMetafactory is in the JDK and its implementation has a property to write out ("dump") + * generated classes, which we take advantage of here. This property can be set externally, and in + * that case the specified directory is used as a temporary dir. Otherwise, it will be set here, + * before doing anything else since the property is read in the static initializer. + */ + static Path createAndRegisterLambdaDumpDirectory() { + String propertyValue = System.getProperty(LAMBDA_METAFACTORY_DUMPER_PROPERTY); + if (propertyValue != null) { + Path path = Paths.get(propertyValue); + checkState(Files.isDirectory(path), "The path '%s' is not a directory.", path); + // It is not necessary to check whether 'path' is an empty directory. It is possible that + // LambdaMetafactory is loaded before this class, and there are already lambda classes dumped + // into the 'path' folder. + // TODO(kmb): Maybe we can empty the folder here. + return path; + } + + Path dumpDirectory; + try { + dumpDirectory = Files.createTempDirectory("lambdas"); + } catch (IOException e) { + throw new IOError(e); + } + System.setProperty(LAMBDA_METAFACTORY_DUMPER_PROPERTY, dumpDirectory.toString()); + deleteTreeOnExit(dumpDirectory); + return dumpDirectory; + } + + private static ImmutableList toInputOutputPairs(DesugarOptions options) { + final ImmutableList.Builder ioPairListbuilder = ImmutableList.builder(); + for (Iterator inputIt = options.inputJars.iterator(), + outputIt = options.outputJars.iterator(); + inputIt.hasNext(); ) { + ioPairListbuilder.add(InputOutputPair.create(inputIt.next(), outputIt.next())); + } + return ioPairListbuilder.build(); + } + + private static void deleteTreeOnExit(final Path directory) { + Thread shutdownHook = + new Thread() { + @Override + public void run() { + try { + deleteTree(directory); + } catch (IOException e) { + throw new RuntimeException("Failed to delete " + directory, e); + } + } + }; + Runtime.getRuntime().addShutdownHook(shutdownHook); + } + + /** Recursively delete a directory. */ + private static void deleteTree(final Path directory) throws IOException { + if (directory.toFile().exists()) { + Files.walkFileTree( + directory, + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } + } + + /** + * Transform a list of Path to a list of InputFileProvider and register them with the given + * closer. + */ + @SuppressWarnings("MustBeClosedChecker") + @VisibleForTesting + static ImmutableList toRegisteredInputFileProvider( + Closer closer, List paths) throws IOException { + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + for (Path path : paths) { + builder.add(closer.register(InputFileProvider.open(path))); + } + return builder.build(); + } + + /** Pair input and output. */ + @AutoValue + abstract static class InputOutputPair { + + static InputOutputPair create(Path input, Path output) { + return new AutoValue_Desugar_InputOutputPair(input, output); + } + + abstract Path getInput(); + + abstract Path getOutput(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java new file mode 100644 index 00000000000000..2e8ed804906122 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/EmulatedInterfaceRewriter.java @@ -0,0 +1,132 @@ +// Copyright 2018 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.devtools.build.android.desugar.ClassSignatureParser.ClassSignature; +import com.google.devtools.build.android.desugar.io.BitFlags; +import org.objectweb.asm.Opcodes; +import java.util.Collections; +import java.util.LinkedHashSet; +import org.objectweb.asm.ClassVisitor; + +/** + * Visitor that renames emulated interfaces and marks classes that extend emulated interfaces to + * also implement the renamed interfaces. {@link DefaultMethodClassFixer} makes sure the requisite + * methods are present in all classes implementing the renamed interface. Doing this helps with + * dynamic dispatch on emulated interfaces. + */ +public class EmulatedInterfaceRewriter extends ClassVisitor { + + private static final String[] EMPTY_ARRAY = new String[0]; + + private final CoreLibrarySupport support; + + public EmulatedInterfaceRewriter(ClassVisitor dest, CoreLibrarySupport support) { + super(Opcodes.ASM9, dest); + this.support = support; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + boolean emulated = support.isEmulatedCoreClassOrInterface(name); + { + // If signature is already broken (if it is null), we can't salvage it. Bail out. + ClassSignature classSignature = + signature != null + ? ClassSignatureParser.readTypeParametersForInterfaces( + name, signature, superName, interfaces) + : null; + + StringBuilder signatureAdditions = new StringBuilder(); + + // 1. see if we should implement any additional interfaces. + // Use LinkedHashSet to dedupe but maintain deterministic order + LinkedHashSet newInterfaces = new LinkedHashSet<>(); + if (interfaces != null && interfaces.length > 0) { + // Make classes implementing emulated interfaces also implement the renamed interfaces we + // create below. This includes making the renamed interfaces extends each other as needed. + Collections.addAll(newInterfaces, interfaces); + for (int i = 0; i < interfaces.length; i++) { + String itf = interfaces[i]; + + if (support.isEmulatedCoreClassOrInterface(itf)) { + String newInterface = support.renameCoreLibrary(itf); + newInterfaces.add(newInterface); + + if (classSignature != null) { + signatureAdditions + .append("L") + .append(newInterface) + .append(classSignature.interfaceTypeParameters().get(i)) + .append(";"); + } + } + } + } + + if (!emulated) { + // For an immediate subclass of an emulated class, also fill in any interfaces implemented + // by superclasses, similar to the additional default method stubbing performed in + // DefaultMethodClassFixer in this situation. + Class superclass = support.getEmulatedCoreClassOrInterface(superName); + while (superclass != null) { + for (Class implemented : superclass.getInterfaces()) { + String itf = implemented.getName().replace('.', '/'); + if (support.isEmulatedCoreClassOrInterface(itf)) { + String newInterface = support.renameCoreLibrary(itf); + boolean added = newInterfaces.add(newInterface); + + // Make sure we don't generate the same signature for an interface implemented in + // multiple superclasses + if (classSignature != null && added) { + signatureAdditions + .append("L") + .append(newInterface) + // TODO(b/137073683): Handle the case where superclass has additional type + // parameters not used in the emulated interface. + .append(classSignature.superClassSignature().typeParameters()) + .append(";"); + } + } + } + superclass = superclass.getSuperclass(); + } + } + // Update implemented interfaces and signature if we did anything above + if (interfaces == null + ? !newInterfaces.isEmpty() + : interfaces.length != newInterfaces.size()) { + interfaces = newInterfaces.toArray(EMPTY_ARRAY); + if (signature != null) { + // If we had no previous interfaces but have new interfaces from the super class, which + // should still append this additions. Per the specification, it is valid to add these + // interfaces after the superclass signature. + signature += signatureAdditions; + } + } + } + + // 2. see if we need to rename this interface itself + if (BitFlags.isInterface(access) && emulated) { + name = support.renameCoreLibrary(name); + } + super.visit(version, access, name, signature, superName, interfaces); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/GeneratedClassStore.java b/src/tools/android/java/com/google/devtools/build/android/desugar/GeneratedClassStore.java new file mode 100644 index 00000000000000..812274505a5afb --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/GeneratedClassStore.java @@ -0,0 +1,47 @@ +// 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.checkState; + +import com.google.common.collect.ImmutableMap; +import java.util.LinkedHashMap; +import java.util.Map; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.tree.ClassNode; + +/** Simple wrapper around a map that holds generated classes so they can be processed later. */ +class GeneratedClassStore { + + /** Map from internal names to generated classes with deterministic iteration order. */ + private final Map classes = new LinkedHashMap<>(); + + /** + * Adds a class for the given internal name. It's the caller's responsibility to {@link + * ClassVisitor#visit} the returned object to initialize the desired class, and to avoid + * confusion, this method throws if the class had already been present. + */ + public ClassVisitor add(String internalClassName) { + ClassNode result = new ClassNode(); + checkState( + classes.put(internalClassName, result) == null, "Already present: %s", internalClassName); + return result; + } + + public ImmutableMap drain() { + ImmutableMap result = ImmutableMap.copyOf(classes); + classes.clear(); + return result; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java b/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java new file mode 100644 index 00000000000000..6bbdf68633054d --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java @@ -0,0 +1,694 @@ +// 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.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.devtools.build.android.desugar.io.BitFlags; +import com.google.devtools.build.android.desugar.io.FieldInfo; +import com.google.devtools.build.android.r8.DependencyCollector; +import org.objectweb.asm.Opcodes; +import java.lang.reflect.Method; +import java.util.Arrays; +import javax.annotation.Nullable; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.TypePath; + +/** + * Visitor that moves methods with bodies from interfaces into a companion class and rewrites call + * sites accordingly (which is only needed for static interface methods). Default methods are kept + * as abstract methods with all their annotations. + * + *

Any necessary companion classes will be added to the given {@link GeneratedClassStore}. It's + * the caller's responsibility to write those out. + * + *

Relies on {@link DefaultMethodClassFixer} to stub in method bodies for moved default methods. + * Assumes that lambdas are already desugared. Ignores bridge methods, which are handled specially. + */ +class InterfaceDesugaring extends ClassVisitor { + + static final String COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME = "$$triggerInterfaceInit"; + static final String COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC = "()V"; + + static final String DEFAULT_COMPANION_METHOD_SUFFIX = "$$dflt$$"; + + private final boolean generateBaseClasses; + private final ClassVsInterface interfaceCache; + private final DependencyCollector depsCollector; + private final CoreLibrarySupport coreLibrarySupport; + private final ClassReaderFactory bootclasspath; + private final ClassLoader targetLoader; + private final GeneratedClassStore store; + private final boolean legacyJaCoCo; + + private String internalName; + private int bytecodeVersion; + private int accessFlags; + private String[] interfaces; + private int numberOfDefaultMethods; + @Nullable private ClassVisitor companion; + @Nullable private FieldInfo interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit; + + public InterfaceDesugaring( + ClassVisitor dest, + boolean generateBaseClasses, + ClassVsInterface interfaceCache, + DependencyCollector depsCollector, + @Nullable CoreLibrarySupport coreLibrarySupport, + ClassReaderFactory bootclasspath, + ClassLoader targetLoader, + GeneratedClassStore store, + boolean legacyJaCoCo) { + super(Opcodes.ASM9, dest); + this.generateBaseClasses = generateBaseClasses; + this.interfaceCache = interfaceCache; + this.depsCollector = depsCollector; + this.coreLibrarySupport = coreLibrarySupport; + this.bootclasspath = bootclasspath; + this.targetLoader = targetLoader; + this.store = store; + this.legacyJaCoCo = legacyJaCoCo; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + companion = null; + numberOfDefaultMethods = 0; + internalName = name; + bytecodeVersion = version; + accessFlags = access; + this.interfaces = interfaces; + 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); + } + + @Override + public void visitEnd() { + if (companion != null) { + // Record classes with default methods. This increases precision when double-checking + // desugaring results later, without parsing .class files again, compared to just looking + // for companion classes in a given desugared Jar which may only contain static methods. + depsCollector.recordDefaultMethods(internalName, numberOfDefaultMethods); + + // Emit a method to access the fields of the interfaces that need initialization. + emitInterfaceFieldAccessInCompanionMethodToTriggerInterfaceClinit(); + companion.visitEnd(); + } + super.visitEnd(); + } + + private void emitInterfaceFieldAccessInCompanionMethodToTriggerInterfaceClinit() { + if (companion == null + || interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit == null) { + return; + } + + // Create a method to access the interface fields + MethodVisitor visitor = + checkNotNull( + companion.visitMethod( + Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, + COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME, + COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC, + null, + null), + "Cannot get a method visitor to write out %s to the companion class.", + COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME); + // Visit the interface field to triger of the interface. + + visitor.visitFieldInsn( + Opcodes.GETSTATIC, + interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.owner(), + interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.name(), + interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.desc()); + Type fieldType = + Type.getType(interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit.desc()); + if (fieldType.getSort() == Type.LONG || fieldType.getSort() == Type.DOUBLE) { + visitor.visitInsn(Opcodes.POP2); + } else { + visitor.visitInsn(Opcodes.POP); + } + visitor.visitInsn(Opcodes.RETURN); + } + + @Override + public FieldVisitor visitField( + int access, String name, String desc, String signature, Object value) { + if (legacyJaCoCo + && isInterface() + && BitFlags.isSet(access, Opcodes.ACC_FINAL) + && "$jacocoData".equals(name)) { + // Move $jacocoData field to companion class and remove final modifier. We'll rewrite field + // accesses accordingly. Code generated by older JaCoCo versions tried to assign to this + // final field in methods, and interface fields have to be private, so we move the field + // to a class, which ends up looking pretty similar to what JaCoCo generates for classes. + access &= ~Opcodes.ACC_FINAL; + return companion().visitField(access, name, desc, signature, value); + } else { + return super.visitField(access, name, desc, signature, value); + } + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + String codeOwner = internalName; + MethodVisitor result; + if (isInterface() && isStaticInitializer(name)) { + result = + new InterfaceFieldWriteCollector( + super.visitMethod(access, name, desc, signature, exceptions)); + if (result != null && legacyJaCoCo) { + result = new MoveJacocoFieldAccess(result); + } + } else if (isInterface() + && BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_BRIDGE)) { + checkArgument(BitFlags.noneSet(access, Opcodes.ACC_NATIVE), "Forbidden per JLS ch 9.4"); + + boolean isLambdaBody = name.startsWith("lambda$") && BitFlags.isSynthetic(access); + if (isLambdaBody) { + access &= ~Opcodes.ACC_PUBLIC; // undo visibility change from LambdaDesugaring + } + String companionMethodName = + normalizeInterfaceMethodName( + name, + isLambdaBody, + BitFlags.isStatic(access) ? Opcodes.INVOKESTATIC : Opcodes.INVOKESPECIAL); + codeOwner = getCompanionClassName(internalName); + + if (BitFlags.isStatic(access)) { + // Completely move static interface methods, which requires rewriting call sites + result = + companion() + .visitMethod( + access & ~Opcodes.ACC_PRIVATE, + companionMethodName, + desc, + signature, + exceptions); + } else { + MethodVisitor abstractDest; + if (isLambdaBody) { + // Completely move lambda bodies, which requires rewriting call sites + access &= ~Opcodes.ACC_PRIVATE; + abstractDest = null; + } else { + // Make default methods abstract but move their implementation into a static method with + // corresponding signature. Doesn't require callsite rewriting but implementing classes + // may need to implement default methods explicitly. + checkArgument( + BitFlags.noneSet(access, Opcodes.ACC_PRIVATE), + "Unexpected private interface method %s.%s : %s", + name, + internalName, + desc); + ++numberOfDefaultMethods; + if (coreLibrarySupport != null) { + coreLibrarySupport.registerIfEmulatedCoreInterface( + access, internalName, name, desc, exceptions); + } + abstractDest = + super.visitMethod(access | Opcodes.ACC_ABSTRACT, name, desc, signature, exceptions); + + if (generateBaseClasses) { + // Generate base class stub here and now... Write this extra method first so we can + // return the visitor to receive the actual default method body below. + generateStub( + companion().visitMethod(access, name, desc, (String) null, exceptions), + companionMethodName, + companionDefaultMethodDescriptor(internalName, desc)); + } + } + + // TODO(b/37110951): adjust signature with explicit receiver type, which may be generic + // Method visitor that passes through all code but sends annotations into a second given + // MethodVisitor instead. + MethodVisitor codeDest = + companion() + .visitMethod( + access | Opcodes.ACC_STATIC, + companionMethodName, + companionDefaultMethodDescriptor(internalName, desc), + (String) null, // drop signature, since given one doesn't include the new param + exceptions); + + result = abstractDest != null ? new MultiplexAnnotations(codeDest, abstractDest) : codeDest; + } + if (result != null && legacyJaCoCo) { + result = new MoveJacocoFieldAccess(result); + } + } else if (generateBaseClasses + && isInterface() + && ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_BRIDGE | Opcodes.ACC_STATIC)) + == Opcodes.ACC_BRIDGE)) { + // Straight-up move bridge methods to companion alongside other stubs; we would drop them in + // Java7Compatibility anyways + result = companion().visitMethod(access, name, desc, (String) null, exceptions); + } else { + result = super.visitMethod(access, name, desc, signature, exceptions); + } + return result != null + ? new InterfaceInvocationRewriter( + result, + isInterface() ? internalName : null, + bootclasspath, + targetLoader, + depsCollector, + codeOwner) + : null; + } + + private void generateStub(MethodVisitor stubMethod, String calledMethodName, String desc) { + int slot = 0; + Type neededType = Type.getMethodType(desc); + for (Type arg : neededType.getArgumentTypes()) { + stubMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); + slot += arg.getSize(); + } + stubMethod.visitMethodInsn( + Opcodes.INVOKESTATIC, + getCompanionClassName(internalName), + calledMethodName, + desc, + /*isInterface=*/ false); + stubMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); + stubMethod.visitMaxs(slot, slot); + stubMethod.visitEnd(); + } + + @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.isInterface(accessFlags); + } + + private static boolean isStaticInitializer(String methodName) { + return "".equals(methodName); + } + + static String normalizeInterfaceMethodName(String name, boolean isLambda, int opcode) { + 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. + return name + DependencyCollector.INTERFACE_COMPANION_SUFFIX; + } + + switch (opcode) { + case Opcodes.INVOKESPECIAL: + // Rename static methods holding default method implementations since their descriptor + // differs from the original method (due to explicit receiver parameter). This avoids + // possible clashes with static interface methods or generated stubs for default methods + // that could otherwise have the same name and descriptor by coincidence. + return name + DEFAULT_COMPANION_METHOD_SUFFIX; + case Opcodes.INVOKESTATIC: // moved but with same name + return name + "$$STATIC$$"; // TODO(b/117453106): Stop renaming static interface methods + case Opcodes.INVOKEINTERFACE: // not moved + case Opcodes.INVOKEVIRTUAL: // tolerate being called for non-interface methods + return name; + default: + throw new IllegalArgumentException("Unexpected opcode calling " + name + ": " + opcode); + } + } + + static String getCompanionClassName(String interfaceName) { + return interfaceName + DependencyCollector.INTERFACE_COMPANION_SUFFIX; + } + + /** + * Returns the descriptor of a static method for an instance method with the given receiver and + * description, simply by pre-pending the given descriptor's parameter list with the given + * receiver type. + */ + static String companionDefaultMethodDescriptor(String interfaceName, String desc) { + Type type = Type.getMethodType(desc); + Type[] companionArgs = new Type[type.getArgumentTypes().length + 1]; + companionArgs[0] = Type.getObjectType(interfaceName); + System.arraycopy(type.getArgumentTypes(), 0, companionArgs, 1, type.getArgumentTypes().length); + return Type.getMethodDescriptor(type.getReturnType(), companionArgs); + } + + private ClassVisitor companion() { + if (companion == null) { + checkState(isInterface()); + String companionName = getCompanionClassName(internalName); + String[] companionInterfaces; + if (generateBaseClasses) { + // Implement the interface so DefaultMethodClassFixer generates stubs for default methods + // (in addition to the static methods we create here). Thereby the companion class can + // be used as a base class. + companionInterfaces = Arrays.copyOf(interfaces, interfaces.length + 1); + companionInterfaces[interfaces.length] = internalName; + } else { + companionInterfaces = new String[0]; + } + + companion = store.add(companionName); + companion.visit( + bytecodeVersion, + // Companion class must be public so moved methods can be called from anywhere + (accessFlags | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT) + & ~Opcodes.ACC_INTERFACE, + companionName, + (String) null, + "java/lang/Object", + companionInterfaces); + + if (generateBaseClasses) { + MethodVisitor constructor = + companion.visitMethod( + Opcodes.ACC_PUBLIC, "", "()V", (String) null, new String[0]); + constructor.visitCode(); + constructor.visitVarInsn(Opcodes.ALOAD, 0); + constructor.visitMethodInsn( + Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + constructor.visitInsn(Opcodes.RETURN); + constructor.visitMaxs(1, 1); + constructor.visitEnd(); + } + } + return companion; + } + + /** + * Interface field scanner to get the first field of the current interface that is written in the + * initializer. + */ + private class InterfaceFieldWriteCollector extends MethodVisitor { + + public InterfaceFieldWriteCollector(MethodVisitor mv) { + super(Opcodes.ASM9, mv); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit == null + && opcode == Opcodes.PUTSTATIC + && owner.equals(internalName)) { + // It is possible that an interface initializer can sets fields of other classes. + // (b/64290760), so we test whether the owner is the same as the internalName. + interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit = + FieldInfo.create(owner, name, desc); + } + super.visitFieldInsn(opcode, owner, name, desc); + } + } + + /** + * Rewriter for calls to static interface methods and super calls to default methods, unless + * they're part of the bootclasspath, as well as all lambda body methods. Keeps calls to interface + * methods declared in the bootclasspath as-is (but note that these would presumably fail on + * devices without those methods). + */ + static class InterfaceInvocationRewriter extends MethodVisitor { + + /** + * If we're visiting a method declared in an interface, the internal name of that interface. + * That lets us rewrite invocations of other methods within that interface even if the bytecode + * fails to indicate them as interface method invocations, as older versions of JaCoCo failed to + * do (b/62623509). + */ + @Nullable private final String interfaceName; + + private final ClassReaderFactory bootclasspath; + private final ClassLoader targetLoader; + private final DependencyCollector depsCollector; + /** Internal name that'll be used to record any dependencies on interface methods. */ + private final String declaringClass; + + public InterfaceInvocationRewriter( + MethodVisitor dest, + @Nullable String knownInterfaceName, + ClassReaderFactory bootclasspath, + ClassLoader targetLoader, + DependencyCollector depsCollector, + String declaringClass) { + super(Opcodes.ASM9, dest); + this.interfaceName = knownInterfaceName; + this.bootclasspath = bootclasspath; + this.targetLoader = targetLoader; + this.depsCollector = depsCollector; + this.declaringClass = declaringClass; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + // Assume that any static interface methods on the classpath are moved + if ((itf || owner.equals(interfaceName)) && !bootclasspath.isKnown(owner)) { + if (name.startsWith("lambda$")) { + // Redirect lambda invocations to completely remove all lambda methods from interfaces. + checkArgument( + !owner.endsWith(DependencyCollector.INTERFACE_COMPANION_SUFFIX), + "shouldn't consider %s an interface", + owner); + if (opcode == Opcodes.INVOKEINTERFACE) { + opcode = Opcodes.INVOKESTATIC; + desc = companionDefaultMethodDescriptor(owner, desc); + } else { + checkArgument( + opcode == Opcodes.INVOKESTATIC, + "Unexpected opcode %s to invoke %s.%s", + opcode, + owner, + name); + } + // Reflect that InterfaceDesugaring moves and renames the lambda body method + name = normalizeInterfaceMethodName(name, /*isLambda=*/ true, opcode); + owner += DependencyCollector.INTERFACE_COMPANION_SUFFIX; + itf = false; + // Record dependency on companion class + depsCollector.assumeCompanionClass(declaringClass, owner); + + String expectedLambdaMethodName = LambdaDesugaring.uniqueInPackage(owner, name); + checkState( + name.equals(expectedLambdaMethodName), + "Unexpected lambda body method name for %s: real=%s, expected=%s", + owner, + name, + expectedLambdaMethodName); + } else if ((opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL)) { + checkArgument( + !owner.endsWith(DependencyCollector.INTERFACE_COMPANION_SUFFIX), + "shouldn't consider %s an interface", + owner); + if (opcode == Opcodes.INVOKESPECIAL) { + // Turn Interface.super.m() into DefiningInterface$$CC.m(receiver). Note that owner + // always refers to the current type's immediate super-interface, but the default method + // may be inherited by that interface, so we have to figure out where the method is + // defined and invoke it in the corresponding companion class (b/73355452). Note that + // we're always dealing with interfaces here, and all interface methods are public, + // so using Class.getMethods should suffice to find inherited methods. Also note this + // can only be a default method invocation, no abstract method invocation. + owner = + findDefaultMethod(owner, name, desc) + .getDeclaringClass() + .getName() + .replace('.', '/'); + desc = companionDefaultMethodDescriptor(owner, desc); + } + name = normalizeInterfaceMethodName(name, /*isLambda=*/ false, opcode); + owner += DependencyCollector.INTERFACE_COMPANION_SUFFIX; + opcode = Opcodes.INVOKESTATIC; + itf = false; + // Record dependency on companion class + depsCollector.assumeCompanionClass(declaringClass, owner); + } // else non-lambda INVOKEINTERFACE, which needs no rewriting + } + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + + private Method findDefaultMethod(String owner, String name, String desc) { + try { + Class clazz = targetLoader.loadClass(owner.replace('/', '.')); + // otherwise getting public methods with getMethods() below isn't enough + checkArgument(clazz.isInterface(), "Not an interface: %s", owner); + for (Method m : clazz.getMethods()) { + if (m.getName().equals(name) && Type.getMethodDescriptor(m).equals(desc)) { + checkState(m.isDefault(), "Found non-default method: %s", m); + return m; + } + } + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Couldn't load " + owner, e); + } + throw new IllegalArgumentException("Method not found: " + owner + "." + name + desc); + } + } + + /** + * Method visitor intended for interface method bodies that rewrites jacoco field accesses to + * expect the field in the companion class, to work around problematic bytecode emitted by older + * JaCoCo versions (b/62623509). + */ + private static class MoveJacocoFieldAccess extends MethodVisitor { + + public MoveJacocoFieldAccess(MethodVisitor mv) { + super(Opcodes.ASM9, mv); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if ("$jacocoData".equals(name)) { + checkState( + !owner.endsWith(DependencyCollector.INTERFACE_COMPANION_SUFFIX), + "Expected interface: %s", + owner); + owner = getCompanionClassName(owner); + } + super.visitFieldInsn(opcode, owner, name, desc); + } + } + + /** + * Method visitor that behaves like a passthrough but additionally duplicates all annotations into + * a second given {@link MethodVisitor}. + */ + private static class MultiplexAnnotations extends MethodVisitor { + + /** Method visitor for creating desugared interfaces (with static/default methods). */ + private final MethodVisitor annotationOnlyDest; + + public MultiplexAnnotations(@Nullable MethodVisitor dest, MethodVisitor annotationOnlyDest) { + super(Opcodes.ASM9, dest); + this.annotationOnlyDest = annotationOnlyDest; + } + + @Override + public void visitParameter(String name, int access) { + annotationOnlyDest.visitParameter(name, access); + // Intentionally without super call: Method parameter attributes are not supported in Java 7. + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + AnnotationVisitor dest = super.visitAnnotation(desc, visible); + AnnotationVisitor annoDest = annotationOnlyDest.visitAnnotation(desc, visible); + return new MultiplexAnnotationVisitor(dest, annoDest); + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + int typeRef, TypePath typePath, String desc, boolean visible) { + // Intentionally without super call: Type annotations are not supported in Java 7. + return annotationOnlyDest.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + // Intentionally without super call: Production code should depend on the parameter annotation + // attributes of the desugared class instead of the companion class instead, and therefore + // dropping the parameter annotations. Note in the companion class, the corresponding method + // contains one more parameter than the method in the desugared class, a direct propagation + // would cause position mismatch. see b/129719629. + return annotationOnlyDest.visitParameterAnnotation(parameter, desc, visible); + } + } + + /** + * Annotation visitor that recursively passes the visited annotations to any number of given + * {@link AnnotationVisitor}s. + */ + private static class MultiplexAnnotationVisitor extends AnnotationVisitor { + + private final AnnotationVisitor[] moreDestinations; + + public MultiplexAnnotationVisitor( + @Nullable AnnotationVisitor dest, AnnotationVisitor... moreDestinations) { + super(Opcodes.ASM9, dest); + this.moreDestinations = moreDestinations; + } + + @Override + public void visit(String name, Object value) { + super.visit(name, value); + for (AnnotationVisitor dest : moreDestinations) { + dest.visit(name, value); + } + } + + @Override + public void visitEnum(String name, String desc, String value) { + super.visitEnum(name, desc, value); + for (AnnotationVisitor dest : moreDestinations) { + dest.visitEnum(name, desc, value); + } + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + AnnotationVisitor[] subVisitors = new AnnotationVisitor[moreDestinations.length]; + AnnotationVisitor dest = super.visitAnnotation(name, desc); + for (int i = 0; i < subVisitors.length; ++i) { + subVisitors[i] = moreDestinations[i].visitAnnotation(name, desc); + } + return new MultiplexAnnotationVisitor(dest, subVisitors); + } + + @Override + public AnnotationVisitor visitArray(String name) { + AnnotationVisitor[] subVisitors = new AnnotationVisitor[moreDestinations.length]; + AnnotationVisitor dest = super.visitArray(name); + for (int i = 0; i < subVisitors.length; ++i) { + subVisitors[i] = moreDestinations[i].visitArray(name); + } + return new MultiplexAnnotationVisitor(dest, subVisitors); + } + + @Override + public void visitEnd() { + super.visitEnd(); + for (AnnotationVisitor dest : moreDestinations) { + dest.visitEnd(); + } + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/InvokeDynamicLambdaMethodCollector.java b/src/tools/android/java/com/google/devtools/build/android/desugar/InvokeDynamicLambdaMethodCollector.java new file mode 100644 index 00000000000000..2c3b4aea039be4 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/InvokeDynamicLambdaMethodCollector.java @@ -0,0 +1,82 @@ +// 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 com.google.common.collect.ImmutableSet; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; + +/** + * Class visitor to collect all the lambda methods that are used in invokedynamic instructions. + * + *

Note that this class only collects lambda methods. If the invokedynamic is used for other + * purposes, the methods used in the instruction are NOT collected. + */ +class InvokeDynamicLambdaMethodCollector extends ClassVisitor { + + private final ImmutableSet.Builder lambdaMethodsUsedInInvokeDynamic = + ImmutableSet.builder(); + private boolean needOuterClassRewrite = false; + + public InvokeDynamicLambdaMethodCollector() { + super(Opcodes.ASM9); + } + + /** + * Returns whether the visited class is declared in the scope of a lambda. In that case {@link + * LambdaDesugaring} will want to rewrite the EnclosingMethod attribute of the class. + */ + public boolean needOuterClassRewrite() { + return needOuterClassRewrite; + } + + /** Returns methods referenced in invokedynamic instructions that use LambdaMetafactory. */ + public ImmutableSet getLambdaMethodsUsedInInvokeDynamics() { + return lambdaMethodsUsedInInvokeDynamic.build(); + } + + @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 LambdaMethodCollector(mv); + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + needOuterClassRewrite = needOuterClassRewrite || (name != null && name.startsWith("lambda$")); + super.visitOuterClass(owner, name, desc); + } + + private class LambdaMethodCollector extends MethodVisitor { + + public LambdaMethodCollector(MethodVisitor dest) { + super(Opcodes.ASM9, dest); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + if (!"java/lang/invoke/LambdaMetafactory".equals(bsm.getOwner())) { + // Not an invokedynamic for a lambda expression + return; + } + Handle handle = (Handle) bsmArgs[1]; + lambdaMethodsUsedInInvokeDynamic.add( + MethodInfo.create(handle.getOwner(), handle.getName(), handle.getDesc())); + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Java7Compatibility.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Java7Compatibility.java new file mode 100644 index 00000000000000..162dd911c5b3cf --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Java7Compatibility.java @@ -0,0 +1,300 @@ +// 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.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.devtools.build.android.desugar.io.BitFlags; +import org.objectweb.asm.Opcodes; +import javax.annotation.Nullable; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.TypePath; + +/** + * Visitor that tries to ensures bytecode version <= 51 (Java 7) and that throws if it sees default + * or static interface methods (i.e., non-abstract interface methods), which don't exist in Java 7. + * + *

The class version will 52 iff static interface method from the bootclasspath is invoked. This + * is mostly ensure that the generated bytecode is valid. + */ +public class Java7Compatibility extends ClassVisitor { + + @Nullable private final ClassReaderFactory factory; + @Nullable private final ClassReaderFactory bootclasspathReader; + + private boolean isInterface; + private String internalName; + private int access; + private String signature; + private String superName; + private String[] interfaces; + + public Java7Compatibility( + ClassVisitor cv, ClassReaderFactory factory, ClassReaderFactory bootclasspathReader) { + super(Opcodes.ASM9, cv); + this.factory = factory; + this.bootclasspathReader = bootclasspathReader; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + internalName = name; + this.access = access; + this.signature = signature; + this.superName = superName; + this.interfaces = interfaces; + isInterface = BitFlags.isSet(access, Opcodes.ACC_INTERFACE); + // ASM uses the high 16 bits for the minor version: + // https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html#visit-int-int-java.lang.String-java.lang.String-java.lang.String-java.lang.String:A- + // See https://github.com/bazelbuild/bazel/issues/6299 for an example of a class file with a + // non-zero minor version. + int major = version & 0xffff; + if (major > Opcodes.V1_7) { + version = Opcodes.V1_7; + } + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + // Remove bridge default methods in interfaces; javac generates them again for implementing + // classes anyways. + if (isInterface + && (access & (Opcodes.ACC_BRIDGE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC)) + == Opcodes.ACC_BRIDGE) { + return null; + } + if (isInterface + && "$jacocoInit".equals(name) + && BitFlags.isSet(access, Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC)) { + // Drop static interface method that Jacoco generates--we'll inline it into the static + // initializer instead + return null; + } + checkArgument( + !isInterface || BitFlags.isSet(access, Opcodes.ACC_ABSTRACT) || "".equals(name), + "Interface %s defines non-abstract method %s%s, which is not supported", + internalName, + name, + desc); + MethodVisitor result = + new UpdateBytecodeVersionIfNecessary( + super.visitMethod(access, name, desc, signature, exceptions)); + + return (isInterface && "".equals(name)) ? new InlineJacocoInit(result) : result; + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + // Drop MethodHandles$Lookup inner class information--it shouldn't be needed anymore. Proguard + // complains about this even though the inner class information is never used. + if (!"java/lang/invoke/MethodHandles$Lookup".equals(name)) { + super.visitInnerClass(name, outerName, innerName, access); + } + } + + /** This will rewrite class version to 52 if it sees invokestatic on an interface. */ + private class UpdateBytecodeVersionIfNecessary extends MethodVisitor { + + boolean updated = false; + + public UpdateBytecodeVersionIfNecessary(MethodVisitor methodVisitor) { + super(Opcodes.ASM9, methodVisitor); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (itf && opcode == Opcodes.INVOKESTATIC) { + checkNotNull(bootclasspathReader); + checkState( + bootclasspathReader.isKnown(owner), + "%s contains invocation of static interface method that is " + + "not in the bootclasspath. Owner: %s, name: %s, desc: %s.", + Java7Compatibility.this.internalName, + owner, + name, + desc); + if (!updated) { + Java7Compatibility.this.cv.visit( + Opcodes.V1_8, + Java7Compatibility.this.access, + Java7Compatibility.this.internalName, + Java7Compatibility.this.signature, + Java7Compatibility.this.superName, + Java7Compatibility.this.interfaces); + updated = true; + } + } + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + private class InlineJacocoInit extends MethodVisitor { + public InlineJacocoInit(MethodVisitor dest) { + super(Opcodes.ASM9, dest); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == Opcodes.INVOKESTATIC + && "$jacocoInit".equals(name) + && internalName.equals(owner)) { + checkNotNull(factory); + ClassReader bytecode = + checkNotNull( + factory.readIfKnown(internalName), + "Couldn't load interface %s to inline $jacocoInit()", + internalName); + InlineOneMethod copier = new InlineOneMethod("$jacocoInit", this); + bytecode.accept(copier, ClassReader.SKIP_DEBUG /* we're copying generated code anyway */); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + } + + private static class InlineOneMethod extends ClassVisitor { + + private final String methodName; + private final MethodVisitor dest; + private int copied = 0; + + public InlineOneMethod(String methodName, MethodVisitor dest) { + super(Opcodes.ASM9); + this.methodName = methodName; + this.dest = dest; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE)); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals(methodName)) { + checkState(copied == 0, "Found unexpected second method %s with descriptor %s", name, desc); + ++copied; + return new InlineMethodBody(dest); + } + return null; + } + } + + private static class InlineMethodBody extends MethodVisitor { + private final MethodVisitor dest; + + public InlineMethodBody(MethodVisitor dest) { + // We'll set the destination visitor in visitCode() to reduce the risk of copying anything + // we didn't mean to copy + super(Opcodes.ASM9, (MethodVisitor) null); + this.dest = dest; + } + + @Override + public void visitParameter(String name, int access) {} + + @Override + public AnnotationVisitor visitAnnotationDefault() { + throw new IllegalStateException("Don't use to copy annotation attributes"); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return null; + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + int typeRef, TypePath typePath, String desc, boolean visible) { + return null; + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + return null; + } + + @Override + public void visitAttribute(Attribute attr) { + mv = null; // don't care about anything but the code attribute + } + + @Override + public void visitCode() { + // Start copying instructions but don't call super.visitCode() since dest is already in the + // middle of visiting another method + mv = dest; + } + + @Override + public void visitInsn(int opcode) { + switch (opcode) { + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + case Opcodes.RETURN: + checkState(mv != null, "Encountered a second return it would seem: %s", opcode); + mv = null; // Done: we don't expect anything to follow + return; + default: + super.visitInsn(opcode); + } + } + + @Override + public void visitVarInsn(int opcode, int var) { + throw new UnsupportedOperationException( + "We don't support inlining methods with locals: " + opcode + " " + var); + } + + @Override + public void visitLocalVariable( + String name, String desc, String signature, Label start, Label end, int index) { + throw new UnsupportedOperationException( + "We don't support inlining methods with locals: " + name + ": " + desc); + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + // Drop this, since dest will get more instructions and will need to recompute stack size. + // This does indicate the end of visiting bytecode instructions, so defensively reset mv. + mv = null; + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java new file mode 100644 index 00000000000000..626e1ef3628f48 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java @@ -0,0 +1,536 @@ +// 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.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.android.desugar.io.BitFlags; +import org.objectweb.asm.Opcodes; +import java.util.LinkedHashSet; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; + +/** + * Visitor intended to fix up lambda classes to match assumptions made in {@link LambdaDesugaring}. + * Specifically this includes fixing visibilities and generating any missing factory methods. + * + *

Each instance can only visit one class. This is because the signature of the needed factory + * method is passed into the constructor. + */ +class LambdaClassFixer extends ClassVisitor { + + /** Magic method name used by {@link java.lang.invoke.LambdaMetafactory}. */ + public static final String FACTORY_METHOD_NAME = "get$Lambda"; + /** Field name we'll use to hold singleton instances where possible. */ + public static final String SINGLETON_FIELD_NAME = "$instance"; + + private final LambdaInfo lambdaInfo; + private final ClassReaderFactory factory; + private final ImmutableSet interfaceLambdaMethods; + private final boolean allowDefaultMethods; + private final boolean copyBridgeMethods; + private final ClassLoader classLoader; + private final LinkedHashSet implementedMethods = new LinkedHashSet<>(); + private final LinkedHashSet methodsToMoveIn = new LinkedHashSet<>(); + + private String originalInternalName; + private ImmutableList interfaces; + + private boolean hasState; + private boolean hasFactory; + + private String desc; + private String signature; + + public LambdaClassFixer( + ClassVisitor dest, + LambdaInfo lambdaInfo, + ClassReaderFactory factory, + ClassLoader classLoader, + ImmutableSet interfaceLambdaMethods, + boolean allowDefaultMethods, + boolean copyBridgeMethods) { + super(Opcodes.ASM9, dest); + checkArgument(!allowDefaultMethods || interfaceLambdaMethods.isEmpty()); + checkArgument(allowDefaultMethods || copyBridgeMethods); + this.lambdaInfo = lambdaInfo; + this.factory = factory; + this.classLoader = classLoader; + this.interfaceLambdaMethods = interfaceLambdaMethods; + this.allowDefaultMethods = allowDefaultMethods; + this.copyBridgeMethods = copyBridgeMethods; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkArgument(BitFlags.noneSet(access, Opcodes.ACC_INTERFACE), "Not a class: %s", name); + checkState(this.originalInternalName == null, "not intended for reuse but reused for %s", name); + originalInternalName = name; + hasState = false; + hasFactory = false; + desc = null; + this.signature = null; + this.interfaces = ImmutableList.copyOf(interfaces); + // Rename to desired name + super.visit(version, access, getInternalName(), signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField( + int access, String name, String desc, String signature, Object value) { + hasState = true; + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("writeReplace") + && BitFlags.noneSet(access, Opcodes.ACC_STATIC) + && desc.equals("()Ljava/lang/Object;")) { + // Lambda serialization hooks use java/lang/invoke/SerializedLambda, which isn't available on + // Android. Since Jack doesn't do anything special for serializable lambdas we just drop these + // serialization hooks. + // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/output.html#a5324 gives + // details on the role and signature of this method. + return null; + } + if (BitFlags.noneSet(access, Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC)) { + // Keep track of instance methods implemented in this class for later. Since this visitor + // is intended for lambda classes, no need to look at the superclass. + implementedMethods.add(name + ":" + desc); + } + if (FACTORY_METHOD_NAME.equals(name)) { + hasFactory = true; + if (!lambdaInfo.needFactory()) { + return null; // drop generated factory method if we won't call it + } + access &= ~Opcodes.ACC_PRIVATE; // make factory method accessible + } else if ("".equals(name)) { + this.desc = desc; + this.signature = signature; + if (!lambdaInfo.needFactory() && !desc.startsWith("()")) { + access &= ~Opcodes.ACC_PRIVATE; // make constructor accessible if we'll call it directly + } + } + MethodVisitor methodVisitor = + new LambdaClassMethodRewriter(super.visitMethod(access, name, desc, signature, exceptions)); + if (!lambdaInfo.bridgeMethod().equals(lambdaInfo.methodReference())) { + // Skip UseBridgeMethod unless we actually need it + methodVisitor = + new UseBridgeMethod( + methodVisitor, lambdaInfo, classLoader, access, name, desc, signature, exceptions); + } + if (!FACTORY_METHOD_NAME.equals(name) && !"".equals(name)) { + methodVisitor = new LambdaClassInvokeSpecialRewriter(methodVisitor); + } + methodVisitor.visitLineNumber(lambdaInfo.lineNumber(), new Label()); + return methodVisitor; + } + + @Override + public void visitEnd() { + checkState( + !hasState || hasFactory, + "Expected factory method for capturing lambda %s", + getInternalName()); + if (lambdaInfo.sourceFilename().isPresent()) { + super.visitSource(lambdaInfo.sourceFilename().get(), /* debug= */ null); + } + if (!hasState) { + checkState( + signature == null, + "Didn't expect generic constructor signature %s %s", + getInternalName(), + signature); + checkState( + lambdaInfo.factoryMethodDesc().startsWith("()"), + "Expected 0-arg factory method for %s but found %s", + getInternalName(), + lambdaInfo.factoryMethodDesc()); + // Since this is a stateless class we populate and use a static singleton field "$instance". + // Field is package-private so we can read it from the class that had the invokedynamic. + String singletonFieldDesc = lambdaInfo.factoryMethodDesc().substring("()".length()); + super.visitField( + Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, + SINGLETON_FIELD_NAME, + singletonFieldDesc, + (String) null, + (Object) null) + .visitEnd(); + MethodVisitor codeBuilder = + super.visitMethod(Opcodes.ACC_STATIC, "", "()V", (String) null, new String[0]); + codeBuilder.visitTypeInsn(Opcodes.NEW, getInternalName()); + codeBuilder.visitInsn(Opcodes.DUP); + codeBuilder.visitMethodInsn( + Opcodes.INVOKESPECIAL, + getInternalName(), + "", + checkNotNull(desc, "didn't see a constructor for %s", getInternalName()), + /*isInterface=*/ false); + codeBuilder.visitFieldInsn( + Opcodes.PUTSTATIC, getInternalName(), SINGLETON_FIELD_NAME, singletonFieldDesc); + codeBuilder.visitInsn(Opcodes.RETURN); + codeBuilder.visitMaxs(2, 0); // two values are pushed onto the stack + codeBuilder.visitEnd(); + } + + copyRewrittenLambdaMethods(); + if (copyBridgeMethods) { + copyBridgeMethods(interfaces); + } + super.visitEnd(); + } + + private String getInternalName() { + return lambdaInfo.desiredInternalName(); + } + + private void copyRewrittenLambdaMethods() { + for (String rewritten : methodsToMoveIn) { + String interfaceInternalName = rewritten.substring(0, rewritten.indexOf('#')); + String methodName = rewritten.substring(interfaceInternalName.length() + 1); + ClassReader bytecode = + checkNotNull( + factory.readIfKnown(interfaceInternalName), + "Couldn't load interface with lambda method %s", + rewritten); + CopyOneMethod copier = new CopyOneMethod(methodName); + // TODO(kmb): Set source file attribute for lambda classes so lambda debug info makes sense + bytecode.accept(copier, ClassReader.SKIP_DEBUG); + checkState(copier.copied(), "Didn't find %s", rewritten); + } + } + + private void copyBridgeMethods(ImmutableList interfaces) { + for (String implemented : interfaces) { + ClassReader bytecode = factory.readIfKnown(implemented); + if (bytecode != null) { + // Don't copy line numbers and local variable tables. They would be misleading or wrong + // and other methods in generated lambda classes don't have debug info either. + bytecode.accept(new CopyBridgeMethods(), ClassReader.SKIP_DEBUG); + } // else the interface is defined in a different Jar, which we can ignore here + } + } + + /** Rewriter for methods in generated lambda classes. */ + private class LambdaClassMethodRewriter extends MethodVisitor { + public LambdaClassMethodRewriter(MethodVisitor dest) { + super(Opcodes.ASM9, dest); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + String method = owner + "#" + name; + if (interfaceLambdaMethods.contains(method)) { + // Rewrite invocations of lambda methods in interfaces to anticipate the lambda method being + // moved into the lambda class (i.e., the class being visited here). + checkArgument(opcode == Opcodes.INVOKESTATIC, "Cannot move instance method %s", method); + owner = getInternalName(); + itf = false; // owner was interface but is now a class + methodsToMoveIn.add(method); + } else if (originalInternalName.equals(owner)) { + // Reflect renaming of lambda classes + owner = getInternalName(); + } + + if (name.startsWith("lambda$")) { + // Reflect renaming of lambda$ instance methods in LambdaDesugaring. Do this even if we'll + // move the method into the lambda class we're processing so the renaming done in + // LambdaDesugaring doesn't kick in if the class were desugared a second time. + name = LambdaDesugaring.uniqueInPackage(owner, name); + } + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + if (originalInternalName.equals(type)) { + // Reflect renaming of lambda classes + type = getInternalName(); + } + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (originalInternalName.equals(owner)) { + // Reflect renaming of lambda classes + owner = getInternalName(); + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + // Drop annotation that's part of the generated lambda class that's not available on Android. + // Proguard complains about this otherwise. + if ("Ljava/lang/invoke/LambdaForm$Hidden;".equals(desc)) { + return null; + } + return super.visitAnnotation(desc, visible); + } + } + + /** Rewriter for invokespecial in generated lambda classes. */ + private static class LambdaClassInvokeSpecialRewriter extends MethodVisitor { + + public LambdaClassInvokeSpecialRewriter(MethodVisitor dest) { + super(Opcodes.ASM9, dest); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == Opcodes.INVOKESPECIAL && name.startsWith("lambda$")) { + opcode = itf ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL; + } + + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + /** + * Visitor that copies bridge methods from the visited interface into the class visited by the + * surrounding {@link LambdaClassFixer}. Descends recursively into interfaces extended by the + * visited interface. + */ + private class CopyBridgeMethods extends ClassVisitor { + + @SuppressWarnings("hiding") + private ImmutableList interfaces; + + public CopyBridgeMethods() { + // No delegate visitor; instead we'll add methods to the outer class's delegate where needed + super(Opcodes.ASM9); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE)); + checkState(this.interfaces == null); + this.interfaces = ImmutableList.copyOf(interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if ((access & (Opcodes.ACC_BRIDGE | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC)) + == Opcodes.ACC_BRIDGE) { + // Only copy bridge methods--hand-written default methods are not supported--and only if + // we haven't seen the method already. + if (implementedMethods.add(name + ":" + desc)) { + MethodVisitor result = + LambdaClassFixer.super.visitMethod(access, name, desc, signature, exceptions); + return allowDefaultMethods ? result : new AvoidJacocoInit(result); + } + } + return null; + } + + @Override + public void visitEnd() { + copyBridgeMethods(this.interfaces); + } + } + + private class CopyOneMethod extends ClassVisitor { + + private final String methodName; + private int copied = 0; + + public CopyOneMethod(String methodName) { + // No delegate visitor; instead we'll add methods to the outer class's delegate where needed + super(Opcodes.ASM9); + checkState(!allowDefaultMethods, "Couldn't copy interface lambda bodies"); + this.methodName = methodName; + } + + public boolean copied() { + return copied > 0; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkArgument(BitFlags.isSet(access, Opcodes.ACC_INTERFACE)); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals(methodName)) { + checkState(copied == 0, "Found unexpected second method %s with descriptor %s", name, desc); + ++copied; + // Rename for consistency with what we do in LambdaClassMethodRewriter + name = LambdaDesugaring.uniqueInPackage(getInternalName(), name); + return new AvoidJacocoInit( + LambdaClassFixer.super.visitMethod(access, name, desc, signature, exceptions)); + } + return null; + } + } + + /** + * Method visitor that rewrites {@code $jacocoInit()} calls to equivalent field accesses. + * + *

This class should only be used to visit interface methods and assumes that the code in + * {@code $jacocoInit()} is always executed in the interface's static initializer, which is the + * case in the absence of hand-written static or default interface methods (which {@link + * Java7Compatibility} makes sure of). + */ + private static class AvoidJacocoInit extends MethodVisitor { + public AvoidJacocoInit(MethodVisitor dest) { + super(Opcodes.ASM9, dest); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == Opcodes.INVOKESTATIC && "$jacocoInit".equals(name)) { + // Rewrite $jacocoInit() calls to just read the $jacocoData field + super.visitFieldInsn(Opcodes.GETSTATIC, owner, "$jacocoData", "[Z"); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + } + + private static class UseBridgeMethod extends MethodNode { + + private final MethodVisitor dest; + private final LambdaInfo lambdaInfo; + private final ClassLoader classLoader; + + public UseBridgeMethod( + MethodVisitor dest, + LambdaInfo lambdaInfo, + ClassLoader classLoader, + int access, + String name, + String desc, + String signature, + String[] exceptions) { + super(Opcodes.ASM9, access, name, desc, signature, exceptions); + this.dest = dest; + this.lambdaInfo = lambdaInfo; + this.classLoader = classLoader; + checkArgument( + !lambdaInfo.methodReference().equals(lambdaInfo.bridgeMethod()), + "This class only works for a lambda that has a bridge method. lambdaInfo=%s, bridge=%s", + lambdaInfo.methodReference(), + lambdaInfo.bridgeMethod()); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (!name.equals(lambdaInfo.methodReference().getName()) + || !desc.equals(lambdaInfo.methodReference().getDesc())) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + + boolean useBridgeMethod = false; + if (owner.equals(lambdaInfo.methodReference().getOwner())) { + if (lambdaInfo.methodReference().getTag() == Opcodes.H_NEWINVOKESPECIAL + && lambdaInfo.bridgeMethod().getTag() != Opcodes.H_NEWINVOKESPECIAL) { + // We're changing a constructor call to a factory method call, so we unfortunately need + // to go find the NEW/DUP pair preceding the constructor call and remove it + removeLastAllocation(); + } + useBridgeMethod = true; + } else if ((lambdaInfo.methodReference().getTag() == Opcodes.H_INVOKEVIRTUAL + || lambdaInfo.methodReference().getTag() == Opcodes.H_INVOKESPECIAL) + && hasAssignableRelation(owner, lambdaInfo.methodReference().getOwner())) { + // For rewriting instance methods calls, we consider the class hierarchy. + // This is for JDK 9: (b/62218600). + // TODO(cnsun): revisit this to make sure Desugar is fully compatible with this change + // in JDK: http://hg.openjdk.java.net/jdk9/dev/jdk/rev/a3b3c7b6464d + useBridgeMethod = true; + } + if (useBridgeMethod) { + super.visitMethodInsn( + LambdaDesugaring.invokeOpcode(lambdaInfo.bridgeMethod()), + lambdaInfo.bridgeMethod().getOwner(), + lambdaInfo.bridgeMethod().getName(), + lambdaInfo.bridgeMethod().getDesc(), + lambdaInfo.bridgeMethod().isInterface()); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + private void removeLastAllocation() { + AbstractInsnNode insn = instructions.getLast(); + while (insn != null && insn.getPrevious() != null) { + AbstractInsnNode prev = insn.getPrevious(); + if (prev.getOpcode() == Opcodes.NEW + && insn.getOpcode() == Opcodes.DUP + && ((TypeInsnNode) prev).desc.equals(lambdaInfo.methodReference().getOwner())) { + instructions.remove(prev); + instructions.remove(insn); + return; + } + insn = prev; + } + throw new IllegalStateException( + "Couldn't find allocation to rewrite ::new reference " + lambdaInfo.methodReference()); + } + + private boolean hasAssignableRelation(String ownerOfMethodInsn, String ownerOfMethodReference) { + try { + Class methodInsnOwnerClass = classLoader.loadClass(ownerOfMethodInsn.replace('/', '.')); + Class methodReferenceOwnerClass = + classLoader.loadClass(ownerOfMethodReference.replace('/', '.')); + return methodInsnOwnerClass.isAssignableFrom(methodReferenceOwnerClass) + || methodReferenceOwnerClass.isAssignableFrom(methodInsnOwnerClass); + } catch (ClassNotFoundException e) { + throw new IllegalStateException( + "Failed to load method owners for inserting bridge method: " + lambdaInfo, e); + } + } + + @Override + public void visitEnd() { + accept(dest); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java b/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java new file mode 100644 index 00000000000000..3627918138f0d5 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java @@ -0,0 +1,102 @@ +// 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.base.Preconditions.checkArgument; +import static com.google.common.collect.Iterables.getOnlyElement; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +class LambdaClassMaker { + + static final String LAMBDA_METAFACTORY_DUMPER_PROPERTY = "jdk.internal.lambda.dumpProxyClasses"; + + private final Path rootDirectory; + private final Map generatedClasses = new LinkedHashMap<>(); + private final Set existingPaths = new LinkedHashSet<>(); + + public LambdaClassMaker(Path rootDirectory) { + checkArgument( + Files.isDirectory(rootDirectory), "The argument '%s' is not a directory.", rootDirectory); + this.rootDirectory = rootDirectory; + } + + public void generateLambdaClass( + String invokerInternalName, + LambdaInfo lambdaInfo, + MethodHandle bootstrapMethod, + ArrayList bsmArgs) + throws IOException { + // Invoking the bootstrap method will dump the generated class. Ignore any pre-existing + // matching files, which can come from desugar's implementation using classes being desugared. + existingPaths.addAll(findUnprocessed(invokerInternalName + "$$Lambda$")); + try { + bootstrapMethod.invokeWithArguments(bsmArgs); + } catch (Throwable e) { + throw new IllegalStateException( + "Failed to generate lambda class for class " + + invokerInternalName + + " using " + + bootstrapMethod + + " with arguments " + + bsmArgs, + e); + } + + Path generatedClassFile = getOnlyElement(findUnprocessed(invokerInternalName + "$$Lambda$")); + generatedClasses.put(generatedClassFile, lambdaInfo); + existingPaths.add(generatedClassFile); + } + + /** + * Returns absolute paths to .class files generated since the last call to this method together + * with a string descriptor of the factory method. + */ + public ImmutableMap drain() { + ImmutableMap result = ImmutableMap.copyOf(generatedClasses); + generatedClasses.clear(); + return result; + } + + private ImmutableList findUnprocessed(String pathPrefix) throws IOException { + // pathPrefix is an internal class name prefix containing '/', but paths obtained on Windows + // will not contain '/' and searches will fail. So, construct an absolute path from the given + // string and use its string representation to find the file we need regardless of host + // system's file system + Path rootPathPrefix = rootDirectory.resolve(pathPrefix); + final String rootPathPrefixStr = rootPathPrefix.toString(); + + if (!Files.exists(rootPathPrefix.getParent())) { + return ImmutableList.of(); + } + try (Stream paths = Files.list(rootPathPrefix.getParent())) { + return paths + .filter( + path -> + path.toString().startsWith(rootPathPrefixStr) && !existingPaths.contains(path)) + .collect(ImmutableList.toImmutableList()); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java b/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java new file mode 100644 index 00000000000000..ce9176332a4535 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java @@ -0,0 +1,727 @@ +// 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.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.devtools.build.android.desugar.langmodel.ClassName.IN_PROCESS_LABEL_STRIPPER; +import static java.lang.invoke.MethodHandles.publicLookup; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.android.desugar.io.BitFlags; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import org.objectweb.asm.Opcodes; +import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nullable; +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.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; + +/** + * Visitor that desugars classes with uses of lambdas into Java 7-looking code. This includes + * rewriting lambda-related invokedynamic instructions as well as fixing accessibility of methods + * that javac emits for lambda bodies. + * + *

Implementation note: {@link InvokeDynamicLambdaMethodCollector} needs to detect any class that + * this visitor may rewrite, as we conditionally apply this visitor based on it. + */ +class LambdaDesugaring extends ClassVisitor { + + private final ClassLoader targetLoader; + private final LambdaClassMaker lambdas; + private final ImmutableSet.Builder aggregateInterfaceLambdaMethods; + private final Map bridgeMethods = new LinkedHashMap<>(); + private final ImmutableSet lambdaMethodsUsedInInvokeDyanmic; + private final ClassAttributeRecord classAttributeRecord; + private final boolean allowDefaultMethods; + + private String internalName; + private boolean isInterface; + private int lambdaCount; + + public LambdaDesugaring( + ClassVisitor dest, + ClassLoader targetLoader, + LambdaClassMaker lambdas, + ImmutableSet.Builder aggregateInterfaceLambdaMethods, + ImmutableSet lambdaMethodsUsedInInvokeDyanmic, + ClassAttributeRecord classAttributeRecord, + boolean allowDefaultMethods) { + super(Opcodes.ASM9, dest); + this.targetLoader = targetLoader; + this.lambdas = lambdas; + this.aggregateInterfaceLambdaMethods = aggregateInterfaceLambdaMethods; + this.lambdaMethodsUsedInInvokeDyanmic = lambdaMethodsUsedInInvokeDyanmic; + this.classAttributeRecord = classAttributeRecord; + this.allowDefaultMethods = allowDefaultMethods; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkState(internalName == null, "not intended for reuse but reused for %s", name); + internalName = name; + isInterface = BitFlags.isSet(access, Opcodes.ACC_INTERFACE); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitEnd() { + for (Map.Entry bridge : bridgeMethods.entrySet()) { + Handle original = bridge.getKey(); + Handle neededMethod = bridge.getValue().bridgeMethod(); + checkState( + neededMethod.getTag() == Opcodes.H_INVOKESTATIC + || neededMethod.getTag() == Opcodes.H_INVOKEVIRTUAL, + "Cannot generate bridge method %s to reach %s", + neededMethod, + original); + checkState( + bridge.getValue().referenced() != null, + "Need referenced method %s to generate bridge %s", + original, + neededMethod); + + int access = Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL; + if (neededMethod.getTag() == Opcodes.H_INVOKESTATIC) { + access |= Opcodes.ACC_STATIC; + } + MethodVisitor bridgeMethod = + super.visitMethod( + access, + neededMethod.getName(), + neededMethod.getDesc(), + (String) null, + toInternalNames(bridge.getValue().referenced().getExceptionTypes())); + + // Bridge is a factory method calling a constructor + if (original.getTag() == Opcodes.H_NEWINVOKESPECIAL) { + bridgeMethod.visitTypeInsn(Opcodes.NEW, original.getOwner()); + bridgeMethod.visitInsn(Opcodes.DUP); + } + + int slot = 0; + if (neededMethod.getTag() != Opcodes.H_INVOKESTATIC) { + bridgeMethod.visitVarInsn(Opcodes.ALOAD, slot++); + } + Type neededType = Type.getMethodType(neededMethod.getDesc()); + for (Type arg : neededType.getArgumentTypes()) { + bridgeMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); + slot += arg.getSize(); + } + bridgeMethod.visitMethodInsn( + invokeOpcode(original), + original.getOwner(), + original.getName(), + original.getDesc(), + original.isInterface()); + bridgeMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); + + bridgeMethod.visitMaxs(0, 0); // rely on class writer to compute these + bridgeMethod.visitEnd(); + } + super.visitEnd(); + } + + // If this method changes then InvokeDynamicLambdaMethodCollector may need changes well + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("$deserializeLambda$") && BitFlags.isSet(access, Opcodes.ACC_SYNTHETIC)) { + // Android doesn't do anything special for lambda serialization so drop the special + // deserialization hook that javac generates. This also makes sure we don't reference + // java/lang/invoke/SerializedLambda, which doesn't exist on Android. + return null; + } + if (name.startsWith("lambda$") + && BitFlags.isSet(access, Opcodes.ACC_SYNTHETIC) + && lambdaMethodsUsedInInvokeDyanmic.contains(MethodInfo.create(internalName, name, desc))) { + if (!allowDefaultMethods && isInterface && BitFlags.isSet(access, Opcodes.ACC_STATIC)) { + // There must be a lambda in the interface (which in the absence of hand-written default or + // static interface methods must mean it's in the method or inside another lambda). + // We'll move this method out of this class, so just record and drop it here. + // (Note lambda body methods have unique names, so we don't need to remember desc here.) + aggregateInterfaceLambdaMethods.add(internalName + '#' + name); + return null; + } + if (BitFlags.isSet(access, Opcodes.ACC_PRIVATE)) { + // Make lambda body method accessible from lambda class + access &= ~Opcodes.ACC_PRIVATE; + if (allowDefaultMethods && isInterface) { + // java 8 requires interface methods to have exactly one of ACC_PUBLIC and ACC_PRIVATE + access |= Opcodes.ACC_PUBLIC; + } else { + // Method was private so it can be final, which should help VMs perform dispatch. + access |= Opcodes.ACC_FINAL; + } + } + // Guarantee unique lambda body method name to avoid accidental overriding. This wouldn't be + // be necessary for static methods but in visitOuterClass we don't know whether a potential + // outer lambda$ method is static or not, so we just always do it. + name = uniqueInPackage(internalName, name); + } + MethodVisitor dest = super.visitMethod(access, name, desc, signature, exceptions); + return dest != null + ? new InvokedynamicRewriter(dest, access, name, desc, signature, exceptions) + : null; + } + + // If this method changes then InvokeDynamicLambdaMethodCollector may need changes well + @Override + public void visitOuterClass(String owner, String name, String desc) { + if (name != null && name.startsWith("lambda$")) { + // Reflect renaming of lambda$ methods. Proguard gets grumpy if we leave this inconsistent. + name = uniqueInPackage(owner, name); + } + super.visitOuterClass(owner, name, desc); + } + + // When adding visitXxx methods here then InvokeDynamicLambdaMethodCollector may need changes well + + static String uniqueInPackage(String owner, String name) { + String suffix = "$" + owner.substring(owner.lastIndexOf('/') + 1); + // For idempotency, we only attach the package-unique suffix if it isn't there already. This + // prevents a cumulative effect when processing a class more than once (which can happen with + // Bazel, e.g., when re-importing a deploy.jar). During reprocessing, invokedynamics are + // already removed, so lambda$ methods have regular call sites that we would also have to re- + // adjust if we just blindly appended something to lambda$ method names every time we see them. + return name.endsWith(suffix) ? name : name + suffix; + } + + /** + * Makes {@link #visitEnd} generate a bridge method for the given method handle if the referenced + * method will be invisible to the generated lambda class. + * + * @return struct containing either {@code invokedMethod} or {@code invokedMethod} and a handle + * representing the bridge method that will be generated for {@code invokedMethod}. + */ + private MethodReferenceBridgeInfo queueUpBridgeMethodIfNeeded(Handle invokedMethod) + throws ClassNotFoundException { + if (invokedMethod.getName().startsWith("lambda$")) { + // We adjust lambda bodies to be visible + return MethodReferenceBridgeInfo.noBridge(invokedMethod); + } + + // invokedMethod is a method reference if we get here + Executable invoked = findTargetMethod(invokedMethod); + if (isVisibleToLambdaClass(invoked, invokedMethod.getOwner())) { + // Referenced method is visible to the generated class, so nothing to do + return MethodReferenceBridgeInfo.noBridge(invokedMethod); + } + + // We need a bridge method if we get here + checkState( + !isInterface, + "%s is an interface and shouldn't need bridge to %s", + internalName, + invokedMethod); + checkState( + !invokedMethod.isInterface(), + "%s's lambda classes can't see interface method: %s", + internalName, + invokedMethod); + MethodReferenceBridgeInfo result = bridgeMethods.get(invokedMethod); + if (result != null) { + return result; // we're already queued up a bridge method for this method reference + } + + String name = uniqueInPackage(internalName, "bridge$lambda$" + bridgeMethods.size()); + Handle bridgeMethod; + switch (invokedMethod.getTag()) { + case Opcodes.H_INVOKESTATIC: + bridgeMethod = + new Handle( + invokedMethod.getTag(), internalName, name, invokedMethod.getDesc(), /*itf*/ false); + break; + case Opcodes.H_INVOKEVIRTUAL: + case Opcodes.H_INVOKESPECIAL: // we end up calling these using invokevirtual + bridgeMethod = + new Handle( + Opcodes.H_INVOKEVIRTUAL, + internalName, + name, + invokedMethod.getDesc(), /*itf*/ + false); + break; + case Opcodes.H_NEWINVOKESPECIAL: + { + // Call invisible constructor through generated bridge "factory" method, so we need to + // compute the descriptor for the bridge method from the constructor's descriptor + String desc = + Type.getMethodDescriptor( + Type.getObjectType(invokedMethod.getOwner()), + Type.getArgumentTypes(invokedMethod.getDesc())); + bridgeMethod = + new Handle(Opcodes.H_INVOKESTATIC, internalName, name, desc, /*itf*/ false); + break; + } + case Opcodes.H_INVOKEINTERFACE: + // Shouldn't get here + default: + throw new UnsupportedOperationException("Cannot bridge " + invokedMethod); + } + result = MethodReferenceBridgeInfo.bridge(invokedMethod, invoked, bridgeMethod); + MethodReferenceBridgeInfo old = bridgeMethods.put(invokedMethod, result); + checkState(old == null, "Already had bridge %s so we don't also want %s", old, result); + return result; + } + + /** + * Checks whether the referenced method would be visible by an unrelated class in the same package + * as the currently visited class. + */ + private boolean isVisibleToLambdaClass(Executable invoked, String owner) { + int modifiers = invoked.getModifiers(); + if (Modifier.isPrivate(modifiers)) { + return false; + } + if (Modifier.isPublic(modifiers)) { + return true; + } + // invoked is protected or package-private, either way we need it to be in the same package + // because the additional visibility protected gives doesn't help lambda classes, which are in + // a different class hierarchy (and typically just extend Object) + return packageName(internalName).equals(packageName(owner)); + } + + private Executable findTargetMethod(Handle invokedMethod) throws ClassNotFoundException { + Type descriptor = Type.getMethodType(invokedMethod.getDesc()); + Class owner = loadFromInternal(invokedMethod.getOwner()); + if (invokedMethod.getTag() == Opcodes.H_NEWINVOKESPECIAL) { + for (Constructor c : owner.getDeclaredConstructors()) { + if (Type.getType(c).equals(descriptor)) { + return c; + } + } + } else { + for (Method m : owner.getDeclaredMethods()) { + if (m.getName().equals(invokedMethod.getName()) && Type.getType(m).equals(descriptor)) { + return m; + } + } + } + throw new IllegalArgumentException("Referenced method not found: " + invokedMethod); + } + + private Class loadFromInternal(String internalName) throws ClassNotFoundException { + return targetLoader.loadClass(internalName.replace('/', '.')); + } + + static int invokeOpcode(Handle invokedMethod) { + switch (invokedMethod.getTag()) { + case Opcodes.H_INVOKESTATIC: + return Opcodes.INVOKESTATIC; + case Opcodes.H_INVOKEVIRTUAL: + return Opcodes.INVOKEVIRTUAL; + case Opcodes.H_INVOKESPECIAL: + case Opcodes.H_NEWINVOKESPECIAL: // Must be preceded by NEW + return Opcodes.INVOKESPECIAL; + case Opcodes.H_INVOKEINTERFACE: + return Opcodes.INVOKEINTERFACE; + default: + throw new UnsupportedOperationException("Don't know how to call " + invokedMethod); + } + } + + private static String[] toInternalNames(Class[] classes) { + String[] result = new String[classes.length]; + for (int i = 0; i < classes.length; ++i) { + result[i] = Type.getInternalName(classes[i]); + } + return result; + } + + private static String packageName(String internalClassName) { + int lastSlash = internalClassName.lastIndexOf('/'); + return lastSlash > 0 ? internalClassName.substring(0, lastSlash) : ""; + } + + /** + * Desugaring that replaces invokedynamics for {@link java.lang.invoke.LambdaMetafactory} with + * static factory method invocations and triggers a class to be generated for each invokedynamic. + */ + private class InvokedynamicRewriter extends MethodNode { + + private final MethodVisitor dest; + + private int lastLabeledLineNumber; + + public InvokedynamicRewriter( + MethodVisitor dest, + int access, + String name, + String desc, + String signature, + String[] exceptions) { + super(Opcodes.ASM9, access, name, desc, signature, exceptions); + this.dest = checkNotNull(dest, "Null destination for %s.%s : %s", internalName, name, desc); + } + + @Override + public void visitEnd() { + accept(dest); + } + + @Override + public void visitLineNumber(int line, Label start) { + lastLabeledLineNumber = line; + super.visitLineNumber(line, start); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + if (!"java/lang/invoke/LambdaMetafactory".equals(bsm.getOwner())) { + // Not an invokedynamic for a lambda expression + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + return; + } + + try { + Lookup lookup = createLookup(internalName); + ArrayList args = new ArrayList<>(bsmArgs.length + 3); + args.add(lookup); + args.add(name); + args.add(MethodType.fromMethodDescriptorString(desc, targetLoader)); + for (Object bsmArg : bsmArgs) { + args.add(toJvmMetatype(lookup, bsmArg)); + } + + // Both bootstrap methods in LambdaMetafactory expect a MethodHandle as their 5th argument + // so we can assume bsmArgs[1] (the 5th arg) to be a Handle. + MethodReferenceBridgeInfo bridgeInfo = queueUpBridgeMethodIfNeeded((Handle) bsmArgs[1]); + + // Resolve the bootstrap method in "host configuration" (this tool's default classloader) + // since targetLoader may only contain stubs that we can't actually execute. + // generateLambdaClass() below will invoke the bootstrap method, so a stub isn't enough, + // and ultimately we don't care if the bootstrap method was even on the bootclasspath + // when this class was compiled (although it must've been since javac is unhappy otherwise). + MethodHandle bsmMethod = toMethodHandle(publicLookup(), bsm, /*target*/ false); + // Give generated classes to have more stable names (b/35643761). Use BSM's naming scheme + // but with separate counter for each surrounding class. + String lambdaClassName = internalName + "$$Lambda$" + lambdaCount++; + Type[] capturedTypes = Type.getArgumentTypes(desc); + boolean needFactory = + capturedTypes.length != 0 + && !attemptAllocationBeforeArgumentLoads(lambdaClassName, capturedTypes); + Optional sourceFileName = + classAttributeRecord.getSourceFileName( + ClassName.create(internalName).acceptTypeMapper(IN_PROCESS_LABEL_STRIPPER)); + lambdas.generateLambdaClass( + internalName, + LambdaInfo.create( + lambdaClassName, + desc, + needFactory, + bridgeInfo.methodReference(), + bridgeInfo.bridgeMethod(), + sourceFileName, + lastLabeledLineNumber), + bsmMethod, + args); + if (desc.startsWith("()")) { + // For stateless lambda classes we'll generate a singleton instance that we can just load + checkState(capturedTypes.length == 0); + super.visitFieldInsn( + Opcodes.GETSTATIC, + lambdaClassName, + LambdaClassFixer.SINGLETON_FIELD_NAME, + desc.substring("()".length())); + } else if (needFactory) { + // If we were unable to inline the allocation of the generated lambda class then + // invoke factory method of generated lambda class with the arguments on the stack + super.visitMethodInsn( + Opcodes.INVOKESTATIC, + lambdaClassName, + LambdaClassFixer.FACTORY_METHOD_NAME, + desc, + /*itf*/ false); + } else { + // Otherwise we inserted a new/dup pair of instructions above and now just need to invoke + // the constructor of generated lambda class with the arguments on the stack + super.visitMethodInsn( + Opcodes.INVOKESPECIAL, + lambdaClassName, + "", + Type.getMethodDescriptor(Type.VOID_TYPE, capturedTypes), + /*itf*/ false); + } + } catch (IOException | ReflectiveOperationException e) { + throw new IllegalStateException( + "Couldn't desugar invokedynamic for " + + internalName + + "." + + name + + " using " + + bsm + + " with arguments " + + Arrays.toString(bsmArgs), + e); + } + } + + /** + * Tries to insert a new/dup for the given class name before expected existing instructions that + * set up arguments for an invokedynamic factory method with the given types. + * + *

For lambda expressions and simple method references we can assume that arguments are set + * up with loads of the captured (effectively) final variables. But method references, can in + * general capture an expression, such as in {@code myObject.toString()::charAt} (a {@code + * Function<Integer, Character>}), which can also cause null checks to be inserted. In + * such more complicated cases this method may fail to insert a new/dup pair and returns {@code + * false}. + * + * @param internalName internal name of the class to instantiate + * @param paramTypes expected invokedynamic argument types, which also must be the parameters of + * {@code internalName}'s constructor. + * @return {@code true} if we were able to insert a new/dup, {@code false} otherwise + */ + private boolean attemptAllocationBeforeArgumentLoads(String internalName, Type[] paramTypes) { + checkArgument(paramTypes.length > 0, "Expected at least one param for %s", internalName); + // Walk backwards past loads corresponding to constructor arguments to find the instruction + // after which we need to insert our NEW/DUP pair + AbstractInsnNode insn = instructions.getLast(); + for (int i = paramTypes.length - 1; 0 <= i; --i) { + if (insn.getOpcode() == Opcodes.GETFIELD) { + // Lambdas in anonymous inner classes have to load outer scope variables from fields, + // which manifest as an ALOAD followed by one or more GETFIELDs + FieldInsnNode getfield = (FieldInsnNode) insn; + checkState( + getfield.desc.length() == 1 + ? getfield.desc.equals(paramTypes[i].getDescriptor()) + : paramTypes[i].getDescriptor().length() > 1, + "Expected getfield for %s to set up parameter %s for %s but got %s : %s", + paramTypes[i], + i, + internalName, + getfield.name, + getfield.desc); + insn = insn.getPrevious(); + + while (insn.getOpcode() == Opcodes.GETFIELD) { + // Nested inner classes can cause a cascade of getfields from the outermost one inwards + checkState( + ((FieldInsnNode) insn).desc.startsWith("L"), + "expect object type getfields to get to %s to set up parameter %s for %s, not: %s", + paramTypes[i], + i, + internalName, + ((FieldInsnNode) insn).desc); + insn = insn.getPrevious(); + } + + checkState( + insn.getOpcode() == Opcodes.ALOAD, // should be a this pointer to be precise + "Expected aload before getfield for %s to set up parameter %s for %s but got %s", + getfield.name, + i, + internalName, + insn.getOpcode()); + } 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, + // ::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", + paramTypes[i], + i, + internalName, + insn.getOpcode()); + return false; + } + insn = insn.getPrevious(); + } + + TypeInsnNode newInsn = new TypeInsnNode(Opcodes.NEW, internalName); + if (insn == null) { + // Ran off the front of the instruction list + instructions.insert(newInsn); + } else { + instructions.insert(insn, newInsn); + } + instructions.insert(newInsn, new InsnNode(Opcodes.DUP)); + 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 constructor = Lookup.class.getDeclaredConstructor(Class.class); + constructor.setAccessible(true); + return constructor.newInstance(clazz); + } + + /** + * Produces a {@link MethodHandle} or {@link MethodType} using {@link #targetLoader} for the + * given ASM {@link Handle} or {@link Type}. {@code lookup} is only used for resolving {@link + * Handle}s. + */ + private Object toJvmMetatype(Lookup lookup, Object asm) throws ReflectiveOperationException { + if (asm instanceof Number) { + return asm; + } + if (asm instanceof Type) { + Type type = (Type) asm; + switch (type.getSort()) { + case Type.OBJECT: + return loadFromInternal(type.getInternalName()); + case Type.METHOD: + return MethodType.fromMethodDescriptorString(type.getDescriptor(), targetLoader); + default: + throw new IllegalArgumentException("Cannot convert: " + asm); + } + } + if (asm instanceof Handle) { + return toMethodHandle(lookup, (Handle) asm, /*target*/ true); + } + throw new IllegalArgumentException("Cannot convert: " + asm); + } + + /** + * Produces a {@link MethodHandle} using either the context or {@link #targetLoader} class + * loader, depending on {@code target}. + */ + private MethodHandle toMethodHandle(Lookup lookup, Handle asmHandle, boolean target) + throws ReflectiveOperationException { + Class owner = loadFromInternal(asmHandle.getOwner()); + MethodType signature = + MethodType.fromMethodDescriptorString( + asmHandle.getDesc(), + target ? targetLoader : Thread.currentThread().getContextClassLoader()); + switch (asmHandle.getTag()) { + case Opcodes.H_INVOKESTATIC: + return lookup.findStatic(owner, asmHandle.getName(), signature); + case Opcodes.H_INVOKEVIRTUAL: + case Opcodes.H_INVOKEINTERFACE: + return lookup.findVirtual(owner, asmHandle.getName(), signature); + case Opcodes.H_INVOKESPECIAL: // we end up calling these using invokevirtual + return lookup.findSpecial(owner, asmHandle.getName(), signature, owner); + case Opcodes.H_NEWINVOKESPECIAL: + return lookup.findConstructor(owner, signature); + default: + throw new UnsupportedOperationException("Cannot resolve " + asmHandle); + } + } + } + + /** + * Record of how a lambda class can reach its referenced method through a possibly-different + * bridge method. + * + *

In a JVM, lambda classes are allowed to call the referenced methods directly, but we don't + * have that luxury when the generated lambda class is evaluated using normal visibility rules. + */ + @AutoValue + abstract static class MethodReferenceBridgeInfo { + public static MethodReferenceBridgeInfo noBridge(Handle methodReference) { + return new AutoValue_LambdaDesugaring_MethodReferenceBridgeInfo( + methodReference, (Executable) null, methodReference); + } + + public static MethodReferenceBridgeInfo bridge( + Handle methodReference, Executable referenced, Handle bridgeMethod) { + checkArgument(!bridgeMethod.equals(methodReference)); + return new AutoValue_LambdaDesugaring_MethodReferenceBridgeInfo( + methodReference, checkNotNull(referenced), bridgeMethod); + } + + public abstract Handle methodReference(); + + /** Returns {@code null} iff {@link #bridgeMethod} equals {@link #methodReference}. */ + @Nullable + public abstract Executable referenced(); + + public abstract Handle bridgeMethod(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaInfo.java b/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaInfo.java new file mode 100644 index 00000000000000..24e9dc016645ff --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/LambdaInfo.java @@ -0,0 +1,60 @@ +// 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.base.Preconditions.checkArgument; + +import com.google.auto.value.AutoValue; +import java.util.Optional; +import org.objectweb.asm.Handle; + +@AutoValue +abstract class LambdaInfo { + public static LambdaInfo create( + String desiredInternalName, + String factoryMethodDesc, + boolean needFactory, + Handle methodReference, + Handle bridgeMethod, + Optional sourceFileName, + int lineNumber) { + checkArgument( + !needFactory || !factoryMethodDesc.startsWith("()"), + "Shouldn't need a factory method for %s : %s", + desiredInternalName, + factoryMethodDesc); + return new AutoValue_LambdaInfo( + desiredInternalName, + factoryMethodDesc, + needFactory, + methodReference, + bridgeMethod, + sourceFileName, + lineNumber); + } + + public abstract String desiredInternalName(); + + public abstract String factoryMethodDesc(); + /** Returns {@code true} if we need the generated class to have a factory method. */ + public abstract boolean needFactory(); + + public abstract Handle methodReference(); + + public abstract Handle bridgeMethod(); + + public abstract Optional sourceFilename(); + + public abstract int lineNumber(); +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/LongCompareMethodRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/LongCompareMethodRewriter.java new file mode 100644 index 00000000000000..ad1cb703ecd2bb --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/LongCompareMethodRewriter.java @@ -0,0 +1,62 @@ +// 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 org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.LCMP; + +import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +/** + * This class rewrites any call to Long.compare with the JVM instruction lcmp that is semantically + * equivalent to Long.compare. + */ +public class LongCompareMethodRewriter extends ClassVisitor { + + private final CoreLibraryRewriter rewriter; + + public LongCompareMethodRewriter(ClassVisitor cv, CoreLibraryRewriter rewriter) { + super(Opcodes.ASM9, cv); + this.rewriter = rewriter; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor visitor = super.cv.visitMethod(access, name, desc, signature, exceptions); + return visitor == null ? visitor : new LongCompareMethodVisitor(visitor); + } + + private class LongCompareMethodVisitor extends MethodVisitor { + + public LongCompareMethodVisitor(MethodVisitor visitor) { + super(Opcodes.ASM9, visitor); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == INVOKESTATIC + && rewriter.unprefix(owner).equals("java/lang/Long") + && name.equals("compare") + && desc.equals("(JJ)I")) { + super.visitInsn(LCMP); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/MethodInfo.java b/src/tools/android/java/com/google/devtools/build/android/desugar/MethodInfo.java new file mode 100644 index 00000000000000..405554b6d0d530 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/MethodInfo.java @@ -0,0 +1,31 @@ +// 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 com.google.auto.value.AutoValue; + +/** A value class to store the method information. */ +@AutoValue +public abstract class MethodInfo { + + static MethodInfo create(String owner, String name, String desc) { + return new AutoValue_MethodInfo(owner, name, desc); + } + + public abstract String owner(); + + public abstract String name(); + + public abstract String desc(); +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodRewriter.java new file mode 100644 index 00000000000000..dd4f3c12678489 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodRewriter.java @@ -0,0 +1,70 @@ +// 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 org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Opcodes.POP; + +import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +/** + * This class desugars any call to Objects.requireNonNull(Object o), Objects.requireNonNull(Object + * o, String msg), and Objects.requireNonNull(Object o, Supplier msg), by replacing the call with + * o.getClass(). + */ +public class ObjectsRequireNonNullMethodRewriter extends ClassVisitor { + + private final CoreLibraryRewriter rewriter; + + public ObjectsRequireNonNullMethodRewriter(ClassVisitor cv, CoreLibraryRewriter rewriter) { + super(Opcodes.ASM9, cv); + this.rewriter = rewriter; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor visitor = super.cv.visitMethod(access, name, desc, signature, exceptions); + return visitor == null ? visitor : new ObjectsMethodInlinerMethodVisitor(visitor); + } + + private class ObjectsMethodInlinerMethodVisitor extends MethodVisitor { + + public ObjectsMethodInlinerMethodVisitor(MethodVisitor mv) { + super(Opcodes.ASM9, mv); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == INVOKESTATIC + && rewriter.unprefix(owner).equals("java/util/Objects") + && name.equals("requireNonNull") + && desc.equals("(Ljava/lang/Object;)Ljava/lang/Object;")) { + // a call to Objects.requireNonNull(Object o) + // duplicate the first argument 'o', as this method returns 'o'. + super.visitInsn(DUP); + super.visitMethodInsn( + INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); + super.visitInsn(POP); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java new file mode 100644 index 00000000000000..a83506456aba48 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriter.java @@ -0,0 +1,464 @@ +// 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 static org.objectweb.asm.Opcodes.ACC_STATIC; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; +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 com.google.devtools.build.android.desugar.io.BitFlags; +import org.objectweb.asm.Opcodes; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Optional; +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 + * redirect them to ThrowableExtension. + *

  • {@code Throwable.addSuppressed(Throwable)} + *
  • {@code Throwable.getSuppressed()} + *
  • {@code Throwable.printStackTrace()} + *
  • {@code Throwable.printStackTrace(PrintStream)} + *
  • {@code Throwable.printStackTrace(PringWriter)} + */ +public class TryWithResourcesRewriter extends ClassVisitor { + + private static final String RUNTIME_PACKAGE_INTERNAL_NAME = + "com/google/devtools/build/android/desugar/runtime"; + + static final String THROWABLE_EXTENSION_INTERNAL_NAME = + RUNTIME_PACKAGE_INTERNAL_NAME + '/' + "ThrowableExtension"; + + /** The extension classes for java.lang.Throwable. */ + static final ImmutableSet THROWABLE_EXT_CLASS_INTERNAL_NAMES = + ImmutableSet.of( + THROWABLE_EXTENSION_INTERNAL_NAME, + THROWABLE_EXTENSION_INTERNAL_NAME + "$AbstractDesugaringStrategy", + THROWABLE_EXTENSION_INTERNAL_NAME + "$ConcurrentWeakIdentityHashMap", + THROWABLE_EXTENSION_INTERNAL_NAME + "$ConcurrentWeakIdentityHashMap$WeakKey", + THROWABLE_EXTENSION_INTERNAL_NAME + "$MimicDesugaringStrategy", + THROWABLE_EXTENSION_INTERNAL_NAME + "$NullDesugaringStrategy", + THROWABLE_EXTENSION_INTERNAL_NAME + "$ReuseDesugaringStrategy"); + + /** The extension classes for java.lang.Throwable. All the names end with ".class" */ + static final ImmutableSet THROWABLE_EXT_CLASS_INTERNAL_NAMES_WITH_CLASS_EXT = + FluentIterable.from(THROWABLE_EXT_CLASS_INTERNAL_NAMES) + .transform( + new Function() { + @Override + public String apply(String s) { + return s + ".class"; + } + }) + .toSet(); + + static final ImmutableMultimap TARGET_METHODS = + ImmutableMultimap.builder() + .put("addSuppressed", "(Ljava/lang/Throwable;)V") + .put("getSuppressed", "()[Ljava/lang/Throwable;") + .put("printStackTrace", "()V") + .put("printStackTrace", "(Ljava/io/PrintStream;)V") + .put("printStackTrace", "(Ljava/io/PrintWriter;)V") + .build(); + + static final ImmutableMap METHOD_DESC_MAP = + ImmutableMap.builder() + .put("(Ljava/lang/Throwable;)V", "(Ljava/lang/Throwable;Ljava/lang/Throwable;)V") + .put("()[Ljava/lang/Throwable;", "(Ljava/lang/Throwable;)[Ljava/lang/Throwable;") + .put("()V", "(Ljava/lang/Throwable;)V") + .put("(Ljava/io/PrintStream;)V", "(Ljava/lang/Throwable;Ljava/io/PrintStream;)V") + .put("(Ljava/io/PrintWriter;)V", "(Ljava/lang/Throwable;Ljava/io/PrintWriter;)V") + .buildOrThrow(); + + static final String CLOSE_RESOURCE_METHOD_NAME = "$closeResource"; + static final String CLOSE_RESOURCE_METHOD_DESC = + "(Ljava/lang/Throwable;Ljava/lang/AutoCloseable;)V"; + + private final ClassLoader classLoader; + private final Set visitedExceptionTypes; + private final AtomicInteger numOfTryWithResourcesInvoked; + /** Stores the internal class names of resources that need to be closed. */ + private final LinkedHashSet 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 visitedExceptionTypes, + AtomicInteger numOfTryWithResourcesInvoked, + boolean hasCloseResourceMethod) { + super(Opcodes.ASM9, classVisitor); + this.classLoader = classLoader; + this.visitedExceptionTypes = visitedExceptionTypes; + this.numOfTryWithResourcesInvoked = numOfTryWithResourcesInvoked; + this.hasCloseResourceMethod = hasCloseResourceMethod; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + 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 { + // It is possible that all calls to $closeResources(...) are in dead code regions, and the + // calls are eliminated, which leaving the method $closeResources() unused. (b/78030676). + // In this case, we just discard the method body. + checkState( + !hasCloseResourceMethod || closeResourceMethod != null, + "There should be $closeResources(...) in the class file."); + } + super.visitEnd(); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (exceptions != null && exceptions.length > 0) { + // collect exception types. + Collections.addAll(visitedExceptionTypes, exceptions); + } + if (isSyntheticCloseResourceMethod(access, name, desc)) { + checkState(closeResourceMethod == null, "The TWR rewriter has been used."); + closeResourceMethod = + new MethodNode(Opcodes.ASM9, 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); + 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; + } + + 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) { + // A generated class from desugaring a lambda expression or member reference isn't an interface. + if (isDesugaredLambdaClass(className)) { + return false; + } + 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; + /** For debugging purpose. Enrich exception information. */ + private final String internalName; + + private final String methodSignature; + @Nullable private final BytecodeTypeInference typeInference; + + public TryWithResourceVisitor( + String internalName, + String methodSignature, + MethodVisitor methodVisitor, + ClassLoader classLoader, + @Nullable BytecodeTypeInference typeInference) { + super(Opcodes.ASM9, methodVisitor); + this.classLoader = classLoader; + this.internalName = internalName; + this.methodSignature = methodSignature; + this.typeInference = typeInference; + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + if (type != null) { + visitedExceptionTypes.add(type); // type in a try-catch block must extend Throwable. + } + super.visitTryCatchBlock(start, end, handler, type); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + 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()) { + Optional exceptionClassInternalName = exceptionClass.getInternalName(); + checkState( + exceptionClassInternalName.isPresent(), + "The exception %s is not a reference type in %s.%s", + exceptionClass, + internalName, + methodSignature); + checkState( + isAssignableFrom( + "java.lang.Throwable", exceptionClassInternalName.get().replace('/', '.')), + "The exception type %s in %s.%s should be a subclass of java.lang.Throwable.", + exceptionClassInternalName, + internalName, + methodSignature); + } + } + + InferredType resourceType = typeInference.getTypeOfOperandFromTop(0); + Optional resourceClassInternalName = resourceType.getInternalName(); + { + // Check the resource type. + checkState( + resourceClassInternalName.isPresent(), + "The resource class %s is not a reference type in %s.%s", + resourceType, + internalName, + methodSignature); + String resourceClassName = resourceClassInternalName.get().replace('/', '.'); + checkState( + // For a resource class initialized from a lambda expression or an member reference, + // it can implicitly be resolved with a close method. + isDesugaredLambdaClass(resourceClassName) || hasCloseMethod(resourceClassName), + "The resource class %s should have a close() method.", + resourceClassName); + } + resourceTypeInternalNames.add(resourceClassInternalName.get()); + super.visitMethodInsn( + opcode, + owner, + "$closeResource", + "(Ljava/lang/Throwable;L" + resourceClassInternalName.get() + ";)V", + itf); + return; + } + + if (!isMethodCallTargeted(opcode, owner, name, desc)) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + numOfTryWithResourcesInvoked.incrementAndGet(); + visitedExceptionTypes.add(checkNotNull(owner)); // owner extends Throwable. + super.visitMethodInsn( + INVOKESTATIC, THROWABLE_EXTENSION_INTERNAL_NAME, name, METHOD_DESC_MAP.get(desc), false); + } + + private boolean isMethodCallTargeted(int opcode, String owner, String name, String desc) { + if (opcode != INVOKEVIRTUAL) { + return false; + } + if (!TARGET_METHODS.containsEntry(name, desc)) { + return false; + } + 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 hasCloseMethod(String resourceClassName) { + try { + Class klass = classLoader.loadClass(resourceClassName); + klass.getMethod("close"); + return true; + } catch (ClassNotFoundException e) { + throw new AssertionError( + "Failed to load class " + + resourceClassName + + " when desugaring method " + + internalName + + "." + + methodSignature, + e); + } catch (NoSuchMethodException e) { + // There is no close() method in the class, so return false. + return false; + } + } + + private boolean isAssignableFrom(String baseClassName, String subClassName) { + try { + 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 + + " when checking the assignable relation for class " + + baseClassName + + " and " + + subClassName, + e); + } + } + } + + /** + * A class to specialize the method $closeResource(Throwable, AutoCloseable), which does + * + *
      + *
    • Rename AutoCloseable to the given concrete resource type. + *
    • Adjust the invoke instruction that calls AutoCloseable.close() + *
    + */ + 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(Opcodes.ASM9, 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); + } + }; + } + } + + private static boolean isDesugaredLambdaClass(String qualifiedClassName) { + return qualifiedClassName.contains("$$Lambda$"); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/config/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/config/BUILD new file mode 100644 index 00000000000000..213209fcfc10fe --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/config/BUILD @@ -0,0 +1,23 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +java_library( + name = "config", + srcs = glob(["*.java"]), + deps = [ + "//src/main/java/com/google/devtools/common/options", + "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/config/DesugarOptions.java b/src/tools/android/java/com/google/devtools/build/android/desugar/config/DesugarOptions.java new file mode 100644 index 00000000000000..3b2954054e4c7c --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/config/DesugarOptions.java @@ -0,0 +1,310 @@ +/* + * Copyright 2020 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.config; + +import com.google.devtools.build.android.Converters.ExistingPathConverter; +import com.google.devtools.build.android.Converters.PathConverter; +import com.google.devtools.common.options.Option; +import com.google.devtools.common.options.OptionDocumentationCategory; +import com.google.devtools.common.options.OptionEffectTag; +import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.List; + +/** Commandline options for {@link com.google.devtools.build.android.desugar.Desugar}. */ +public class DesugarOptions extends OptionsBase { + + @Option( + name = "input", + allowMultiple = true, + defaultValue = "null", + category = "input", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + converter = ExistingPathConverter.class, + abbrev = 'i', + help = + "Input Jar or directory with classes to desugar (required, the n-th input is paired" + + " with the n-th output).") + public List inputJars; + + @Option( + name = "classpath_entry", + allowMultiple = true, + defaultValue = "null", + category = "input", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + converter = ExistingPathConverter.class, + help = + "Ordered classpath (Jar or directory) to resolve symbols in the --input Jar, like " + + "javac's -cp flag.") + public List classpath; + + @Option( + name = "bootclasspath_entry", + allowMultiple = true, + defaultValue = "null", + category = "input", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + converter = ExistingPathConverter.class, + help = + "Bootclasspath that was used to compile the --input Jar with, like javac's " + + "-bootclasspath flag (required).") + public List bootclasspath; + + @Option( + name = "allow_empty_bootclasspath", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}) + public boolean allowEmptyBootclasspath; + + @Option( + name = "only_desugar_javac9_for_lint", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "A temporary flag specifically for android lint, subject to removal anytime (DO NOT" + + " USE)") + public boolean onlyDesugarJavac9ForLint; + + @Option( + name = "rewrite_calls_to_long_compare", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Rewrite calls to Long.compare(long, long) to the JVM instruction lcmp " + + "regardless of --min_sdk_version.", + category = "misc") + public boolean alwaysRewriteLongCompare; + + @Option( + name = "output", + allowMultiple = true, + defaultValue = "null", + category = "output", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + converter = PathConverter.class, + abbrev = 'o', + help = + "Output Jar or directory to write desugared classes into (required, the n-th output is " + + "paired with the n-th input, output must be a Jar if input is a Jar).") + public List outputJars; + + @Option( + name = "verbose", + defaultValue = "false", + category = "misc", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + abbrev = 'v', + help = "Enables verbose debugging output.") + public boolean verbose; + + @Option( + name = "min_sdk_version", + defaultValue = "1", + category = "misc", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Minimum targeted sdk version. If >= 24, enables default methods in interfaces.") + public int minSdkVersion; + + @Option( + name = "emit_dependency_metadata_as_needed", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Whether to emit META-INF/desugar_deps as needed for later consistency checking.") + public boolean emitDependencyMetadata; + + @Option( + name = "best_effort_tolerate_missing_deps", + defaultValue = "true", + category = "misc", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Whether to tolerate missing dependencies on the classpath in some cases. You should " + + "strive to set this flag to false.") + public boolean tolerateMissingDependencies; + + @Option( + name = "desugar_supported_core_libs", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Enable core library desugaring, which requires configuration with related flags.") + public boolean desugarCoreLibs; + + @Option( + name = "desugar_interface_method_bodies_if_needed", + defaultValue = "true", + category = "misc", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Rewrites default and static methods in interfaces if --min_sdk_version < 24. This " + + "only works correctly if subclasses of rewritten interfaces as well as uses of " + + "static interface methods are run through this tool as well.") + public boolean desugarInterfaceMethodBodiesIfNeeded; + + @Option( + name = "desugar_try_with_resources_if_needed", + defaultValue = "true", + category = "misc", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Rewrites try-with-resources statements if --min_sdk_version < 19.") + public boolean desugarTryWithResourcesIfNeeded; + + @Option( + name = "desugar_try_with_resources_omit_runtime_classes", + defaultValue = "false", + category = "misc", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Omits the runtime classes necessary to support try-with-resources from the output." + + " This property has effect only if --desugar_try_with_resources_if_needed is" + + " used.") + public boolean desugarTryWithResourcesOmitRuntimeClasses; + + @Option( + name = "generate_base_classes_for_default_methods", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "If desugaring default methods, generate abstract base classes for them. " + + "This reduces default method stubs in hand-written subclasses.") + public boolean generateBaseClassesForDefaultMethods; + + @Option( + name = "copy_bridges_from_classpath", + defaultValue = "false", + category = "misc", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Copy bridges from classpath to desugared classes.") + public boolean copyBridgesFromClasspath; + + @Option( + name = "core_library", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Enables rewriting to desugar java.* classes.") + public boolean coreLibrary; + + /** Type prefixes that we'll move to a custom package. */ + @Option( + name = "rewrite_core_library_prefix", + defaultValue = "null", + allowMultiple = true, + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Assume the given java.* prefixes are desugared.") + public List rewriteCoreLibraryPrefixes; + + /** Interfaces whose default and static interface methods we'll emulate. */ + @Option( + name = "emulate_core_library_interface", + defaultValue = "null", + allowMultiple = true, + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Assume the given java.* interfaces are emulated.") + public List emulateCoreLibraryInterfaces; + + /** Members not to rewrite. */ + @Option( + name = "dont_rewrite_core_library_invocation", + defaultValue = "null", + allowMultiple = true, + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Method invocations not to rewrite, given as \"class/Name#method\".") + public List dontTouchCoreLibraryMembers; + + @Option( + name = "auto_desugar_shadowed_api_use", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Enables automatic invocable and overridable desugar-shadowed APIs.") + public boolean autoDesugarShadowedApiUse; + + /** Set to work around b/62623509 with JaCoCo versions prior to 0.7.9. */ + // TODO(kmb): Remove when Android Studio doesn't need it anymore (see b/37116789) + @Option( + name = "legacy_jacoco_fix", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Consider setting this flag if you're using JaCoCo versions prior to 0.7.9 to work" + + " around issues with coverage instrumentation in default and static interface" + + " methods. This flag may be removed when no longer needed.") + public boolean legacyJacocoFix; + + /** Convert Java 11 nest-based access control to bridge-based access control. */ + @Option( + name = "desugar_nest_based_private_access", + defaultValue = "true", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Desugar JVM 11 native supported accessing private nest members with bridge method" + + " based accessors. This flag includes desugaring private interface methods.") + public boolean desugarNestBasedPrivateAccess; + + /** + * Convert Java 9 invokedynamic-based string concatenations to StringBuilder-based + * concatenations. @see https://openjdk.java.net/jeps/280 + */ + @Option( + name = "desugar_indy_string_concat", + defaultValue = "true", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Desugar JVM 9 string concatenation operations to string builder based" + + " implementations.") + public boolean desugarIndifyStringConcat; + + public static DesugarOptions parseCommandLineOptions(String[] args) { + OptionsParser parser = + OptionsParser.builder() + .optionsClasses(DesugarOptions.class) + .allowResidue(false) + .argsPreProcessor(new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault())) + .build(); + parser.parseAndExitUponError(args); + DesugarOptions options = parser.getOptions(DesugarOptions.class); + + return options; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD new file mode 100644 index 00000000000000..0f6252d67f534b --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/BUILD @@ -0,0 +1,32 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +java_library( + name = "corelibadapter", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel:desugar_class_attr_java_proto", + "//src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:flogger", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/InvocationSiteTransformationReason.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/InvocationSiteTransformationReason.java new file mode 100644 index 00000000000000..14a3b4c5b90881 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/InvocationSiteTransformationReason.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020 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.corelibadapter; + +import com.google.auto.value.AutoValue; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; + +/** Tracks the rationale that the desugar tool chooses to transform an method invocation site. */ +@AutoValue +abstract class InvocationSiteTransformationReason { + + enum InvocationSiteTransformationKind { + INLINE_PARAM_TYPE_CONVERSION, + TYPE_ADAPTER_REPLACEMENT, + } + + abstract InvocationSiteTransformationKind kind(); + + abstract MethodKey method(); + + public static InvocationSiteTransformationReason create( + InvocationSiteTransformationKind logReason, MethodKey method) { + return new AutoValue_InvocationSiteTransformationReason(logReason, method); + } + + static InvocationSiteTransformationReason decode(String encodedReason) { + int firstDelimiterPos = encodedReason.indexOf(":"); + InvocationSiteTransformationKind logReason = + InvocationSiteTransformationKind.valueOf(encodedReason.substring(0, firstDelimiterPos)); + MethodKey methodKey = MethodKey.decode(encodedReason.substring(1 + firstDelimiterPos)); + return create(logReason, methodKey); + } + + final String encode() { + return String.format("%s:%s", kind(), method().encode()); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/InvocationSiteTransformationRecord.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/InvocationSiteTransformationRecord.java new file mode 100644 index 00000000000000..7ce5bf8debc042 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/InvocationSiteTransformationRecord.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 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.corelibadapter; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +/** A record that tracks the method invocation transformations. */ +@AutoValue +public abstract class InvocationSiteTransformationRecord { + + abstract ImmutableSet adapterReplacements(); + + abstract ImmutableSet inlineConversions(); + + public static InvocationSiteTransformationRecordBuilder builder() { + return new AutoValue_InvocationSiteTransformationRecord.Builder(); + } + + /** The builder for {@link InvocationSiteTransformationRecord}. */ + @AutoValue.Builder + public abstract static class InvocationSiteTransformationRecordBuilder { + + abstract ImmutableSet.Builder adapterReplacementsBuilder(); + + @CanIgnoreReturnValue + final InvocationSiteTransformationRecordBuilder addAdapterReplacement( + MethodInvocationSite originalMethodInvocationSite) { + adapterReplacementsBuilder().add(originalMethodInvocationSite); + return this; + } + + abstract ImmutableSet.Builder inlineConversionsBuilder(); + + @CanIgnoreReturnValue + final InvocationSiteTransformationRecordBuilder addInlineConversion( + MethodInvocationSite originalMethodInvocationSite) { + inlineConversionsBuilder().add(originalMethodInvocationSite); + return this; + } + + public abstract InvocationSiteTransformationRecord build(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java new file mode 100644 index 00000000000000..9cdb1c575cbe8f --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdapterHelper.java @@ -0,0 +1,226 @@ +/* + * Copyright 2020 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.corelibadapter; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.flogger.GoogleLogger; +import com.google.devtools.build.android.desugar.io.BootClassPathDigest; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MemberUseKind; +import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import com.google.devtools.build.android.desugar.typehierarchy.HierarchicalMethodKey; +import com.google.devtools.build.android.desugar.typehierarchy.HierarchicalMethodQuery; +import com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchy; +import java.util.Optional; +import org.objectweb.asm.Type; + +/** + * Static utilities that serve conversions between desugar-shadowed platform types and their + * desugared-mirrored counterparts. + */ +public class ShadowedApiAdapterHelper { + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + private ShadowedApiAdapterHelper() {} + + /** + * Returns {@code true} if the desugar tool transforms given invocation site in an inline + * strategy, i.e. inserting the parameter type conversion instructions before the give invocation + * site. + * + * @param verbatimInvocationSite The invocation site parsed directly from the desugar input jar. + * No in-process label, such as "__desugar__/", is attached to this invocation site. + * @param typeHierarchy The type hierarchy context of for this query API. + * @param bootClassPathDigest The boot class path context used for complication. + * @param enclosingMethod The method that holds the invocation instruction. + */ + static boolean shouldUseInlineTypeConversion( + MethodInvocationSite verbatimInvocationSite, + TypeHierarchy typeHierarchy, + BootClassPathDigest bootClassPathDigest, + MethodDeclInfo enclosingMethod) { + if (verbatimInvocationSite.invocationKind() != MemberUseKind.INVOKESPECIAL) { + return false; + } + + // invokespecial on a private method in the same class. + if (verbatimInvocationSite.owner().equals(enclosingMethod.owner())) { + return false; + } + + // Absent of desugar-shadowed type in the method header. + if (verbatimInvocationSite.method().getHeaderTypeNameSet().stream() + .noneMatch(ClassName::isDesugarShadowedType)) { + return false; + } + + if (verbatimInvocationSite.isConstructorInvocation()) { + return bootClassPathDigest.containsType(verbatimInvocationSite.owner()); + } + + // Upon on a super call, trace to the adjusted owner with code. + ClassName adjustedGrossOwner = verbatimInvocationSite.owner(); + HierarchicalMethodQuery verbatimMethod = + HierarchicalMethodKey.from(verbatimInvocationSite.method()).inTypeHierarchy(typeHierarchy); + if (!verbatimMethod.isPresent()) { + HierarchicalMethodKey resolvedMethod = verbatimMethod.getFirstBaseClassMethod(); + if (resolvedMethod == null) { + logger.atSevere().log("Missing base method lookup: %s", verbatimInvocationSite); + } else { + adjustedGrossOwner = resolvedMethod.owner().type(); + } + } + return adjustedGrossOwner.isAndroidDomainType() + && bootClassPathDigest.containsType(adjustedGrossOwner); + } + + /** + * Returns {@code true} if the desugar tool transforms given invocation site in an adapter + * strategy, that is to replace the original invocation with its corresponding adapter method. + * + * @param verbatimInvocationSite The invocation site parsed directly from the desugar input jar. + * No in-process label, such as "__desugar__/", is attached to this invocation site. + */ + static boolean shouldUseApiTypeAdapter( + MethodInvocationSite verbatimInvocationSite, BootClassPathDigest bootClassPathDigest) { + return verbatimInvocationSite.invocationKind() != MemberUseKind.INVOKESPECIAL + && verbatimInvocationSite.owner().isAndroidDomainType() + && bootClassPathDigest.containsType(verbatimInvocationSite.owner()) + && verbatimInvocationSite.method().getHeaderTypeNameSet().stream() + .anyMatch(ClassName::isDesugarShadowedType); + } + + /** + * Returns {@code true} if the current method overrides a platform API with desugar-shadowed types + * and should emit an overriding bridge method for the integrity of method dynamic dispatching. + */ + static boolean shouldEmitApiOverridingBridge( + MethodDeclInfo methodDeclInfo, + TypeHierarchy typeHierarchy, + BootClassPathDigest bootClassPathDigest) { + if (bootClassPathDigest.containsType(methodDeclInfo.owner()) + || methodDeclInfo.methodKey().isConstructor() + || methodDeclInfo.isStaticMethod() + || methodDeclInfo.isPrivateAccess() + || methodDeclInfo.headerTypeNameSet().stream() + .noneMatch(ClassName::isDesugarShadowedType)) { + return false; + } + + HierarchicalMethodKey baseMethod = + HierarchicalMethodKey.from(methodDeclInfo.methodKey()) + .inTypeHierarchy(typeHierarchy) + .getFirstBaseClassMethod(); + + boolean queryResult = + baseMethod != null + && baseMethod.owner().type().isAndroidDomainType() + && bootClassPathDigest.containsType(baseMethod.owner().type()); + if (queryResult) { + logger.atInfo().log( + "----> Shadowed Method Overriding Bridge eligible for %s due to base method %s", + methodDeclInfo.methodKey(), baseMethod.toMethodKey()); + } + return queryResult; + } + + /** + * Returns an optional {@link MethodInvocationSite}, present if the given {@link ClassName} is + * eligible for transforming a desugar-mirrored type to a desugar-shadowed platform type. + */ + static Optional anyMirroredToBuiltinTypeConversion(ClassName className) { + return className.isDesugarMirroredType() + ? Optional.of( + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKESTATIC) + .setMethod( + MethodKey.create( + className.mirroredToShadowed().typeConverterOwner(), + "to", + Type.getMethodDescriptor( + className.mirroredToShadowed().toAsmObjectType(), + className.toAsmObjectType()))) + .setIsInterface(false) + .build()) + : Optional.empty(); + } + + /** + * Returns an {@link MethodInvocationSite} that serves transforming a {@code + * shadowedTypeName}-represented type to its desugar-mirrored counterpart. + */ + public static MethodInvocationSite shadowedToMirroredTypeConversionSite( + ClassName shadowedTypeName) { + checkArgument( + shadowedTypeName.isDesugarShadowedType(), + "Expected desugar-shadowed type: Actual (%s)", + shadowedTypeName); + return MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKESTATIC) + .setMethod( + MethodKey.create( + shadowedTypeName.typeConverterOwner(), + "from", + Type.getMethodDescriptor( + shadowedTypeName.shadowedToMirrored().toAsmObjectType(), + shadowedTypeName.toAsmObjectType()))) + .setIsInterface(false) + .build(); + } + + /** + * Returns an {@link MethodInvocationSite} that serves transforming a {@code + * mirroredTypeName}-represented type to its desugar-shadowed counterpart. + */ + static MethodInvocationSite mirroredToShadowedTypeConversionSite(ClassName mirroredTypeName) { + checkArgument( + mirroredTypeName.isDesugarMirroredType(), + "Expected mirrored type: Actual (%s)", + mirroredTypeName); + return MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKESTATIC) + .setMethod( + MethodKey.create( + mirroredTypeName.mirroredToShadowed().typeConverterOwner(), + "to", + Type.getMethodDescriptor( + mirroredTypeName.mirroredToShadowed().toAsmObjectType(), + mirroredTypeName.toAsmObjectType()))) + .setIsInterface(false) + .build(); + } + + /** + * Returns an {@link MethodInvocationSite} that serves as an adapter between desugar-mirrored + * invocations and desugar-shadowed invocations. + */ + static MethodInvocationSite getAdapterInvocationSite(MethodInvocationSite methodInvocationSite) { + return MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKESTATIC) + .setMethod( + methodInvocationSite + .method() + .toAdapterMethodForArgsAndReturnTypes( + methodInvocationSite.isStaticInvocation(), methodInvocationSite.hashCode())) + .setIsInterface(false) + .build(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java new file mode 100644 index 00000000000000..fa5215a8b238d4 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiAdaptersGenerator.java @@ -0,0 +1,187 @@ +/* + * Copyright 2020 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.corelibadapter; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_STATIC; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.devtools.build.android.desugar.io.FileContentProvider; +import com.google.devtools.build.android.desugar.io.MapBasedClassFileProvider; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.stream.Stream; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * Generates type adapter classes with methods that bridge the interactions between + * desugared-mirrored types and their desugar-shadowed built-in types. Together with the necessary + * type converter classes, the class delivers the generated adapter classes to runtime library. + */ +public final class ShadowedApiAdaptersGenerator { + + private static final int TYPE_ADAPTER_CLASS_ACCESS = ACC_PUBLIC | ACC_ABSTRACT | ACC_SYNTHETIC; + private static final int TYPE_CONVERSION_METHOD_ACCESS = ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC; + + /** A pre-collected record that tracks the adapter method requests from invocation sites. */ + private final InvocationSiteTransformationRecord invocationAdapterSites; + + /** A record with evolving map values that track adapter classes to be generated. */ + private final ImmutableMap typeAdapters; + + private ShadowedApiAdaptersGenerator( + InvocationSiteTransformationRecord invocationAdapterSites, + ImmutableMap typeAdapters) { + this.invocationAdapterSites = invocationAdapterSites; + this.typeAdapters = typeAdapters; + } + + /** The public factory method to construct {@link ShadowedApiAdaptersGenerator}. */ + public static ShadowedApiAdaptersGenerator create( + InvocationSiteTransformationRecord callSiteTransformations) { + return emitClassWriters(callSiteTransformations).emitAdapterMethods().closeClassWriters(); + } + + private static ShadowedApiAdaptersGenerator emitClassWriters( + InvocationSiteTransformationRecord callSiteTransformations) { + return new ShadowedApiAdaptersGenerator( + callSiteTransformations, + callSiteTransformations.adapterReplacements().stream() + .map(ShadowedApiAdapterHelper::getAdapterInvocationSite) + .map(MethodInvocationSite::owner) + .distinct() + .collect( + toImmutableMap( + className -> className, + ShadowedApiAdaptersGenerator::createAdapterClassWriter))); + } + + private static ClassWriter createAdapterClassWriter(ClassName className) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + cw.visit( + Opcodes.V1_7, + TYPE_ADAPTER_CLASS_ACCESS, + className.binaryName(), + /* signature= */ null, + /* superName= */ "java/lang/Object", + /* interfaces= */ new String[0]); + return cw; + } + + /** Returns desugar-shadowed API adapters with desugar-mirrored types. */ + public MapBasedClassFileProvider getApiAdapters() { + return MapBasedClassFileProvider.builder() + .setTag("ShadowedApiAdapters") + .setFileContents( + Maps.transformEntries( + typeAdapters, + (className, cw) -> + FileContentProvider.fromBytes(className.classFilePathName(), cw.toByteArray()))) + .build(); + } + + /** + * Returns type conversion classes that converts between a desugar-shadowed type and its + * deusgar-mirrored counterpart. + */ + public ImmutableList getTypeConverters() { + return Stream.concat( + invocationAdapterSites.inlineConversions().stream(), + invocationAdapterSites.adapterReplacements().stream()) + .flatMap( + site -> + Stream.concat(Stream.of(site.returnTypeName()), site.argumentTypeNames().stream())) + .filter(ClassName::isDesugarShadowedType) + .distinct() + .map(ClassName::typeConverterOwner) + .collect(toImmutableList()); + } + + @CanIgnoreReturnValue + private ShadowedApiAdaptersGenerator emitAdapterMethods() { + for (MethodInvocationSite invocationSite : invocationAdapterSites.adapterReplacements()) { + MethodInvocationSite adapterSite = + ShadowedApiAdapterHelper.getAdapterInvocationSite(invocationSite); + ClassName adapterOwner = adapterSite.owner(); + ClassWriter cv = + checkNotNull( + typeAdapters.get(adapterOwner), + "Expected a class writer present before writing its methods. Requested adapter" + + " owner: (%s). Available adapter owners: (%s).", + adapterOwner, + typeAdapters); + MethodKey adapterMethodKey = adapterSite.method(); + MethodDeclInfo adapterMethodDecl = + MethodDeclInfo.create( + adapterMethodKey, + TYPE_ADAPTER_CLASS_ACCESS, + TYPE_CONVERSION_METHOD_ACCESS, + /* signature= */ null, + /* exceptions= */ new String[] {}); + MethodVisitor mv = adapterMethodDecl.accept(cv); + mv.visitCode(); + + int slotOffset = 0; + for (Type argType : adapterMethodDecl.argumentTypes()) { + ClassName argTypeName = ClassName.create(argType); + mv.visitVarInsn(argType.getOpcode(Opcodes.ILOAD), slotOffset); + if (argTypeName.isDesugarMirroredType()) { + MethodInvocationSite conversion = + ShadowedApiAdapterHelper.mirroredToShadowedTypeConversionSite(argTypeName); + conversion.accept(mv); + } + slotOffset += argType.getSize(); + } + + invocationSite.accept(mv); + + ClassName adapterReturnTypeName = adapterMethodDecl.returnTypeName(); + if (adapterReturnTypeName.isDesugarMirroredType()) { + MethodInvocationSite conversion = + ShadowedApiAdapterHelper.shadowedToMirroredTypeConversionSite( + adapterReturnTypeName.mirroredToShadowed()); + conversion.accept(mv); + } + + mv.visitInsn(adapterMethodDecl.returnType().getOpcode(Opcodes.IRETURN)); + + mv.visitMaxs(slotOffset, slotOffset); + mv.visitEnd(); + } + return this; + } + + @CanIgnoreReturnValue + private ShadowedApiAdaptersGenerator closeClassWriters() { + typeAdapters.values().forEach(ClassVisitor::visitEnd); + return this; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java new file mode 100644 index 00000000000000..0a5fb4cbf16354 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter/ShadowedApiInvocationSite.java @@ -0,0 +1,306 @@ +/* + * Copyright 2020 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.corelibadapter; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.devtools.build.android.desugar.corelibadapter.InvocationSiteTransformationReason.InvocationSiteTransformationKind.INLINE_PARAM_TYPE_CONVERSION; +import static com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdapterHelper.shouldUseApiTypeAdapter; +import static com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdapterHelper.shouldUseInlineTypeConversion; +import static com.google.devtools.build.android.desugar.langmodel.ClassName.IMMUTABLE_LABEL_ATTACHER; +import static com.google.devtools.build.android.desugar.langmodel.ClassName.IN_PROCESS_LABEL_STRIPPER; +import static com.google.devtools.build.android.desugar.langmodel.ClassName.SHADOWED_TO_MIRRORED_TYPE_MAPPER; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.flogger.GoogleLogger; +import com.google.devtools.build.android.desugar.corelibadapter.InvocationSiteTransformationRecord.InvocationSiteTransformationRecordBuilder; +import com.google.devtools.build.android.desugar.io.BootClassPathDigest; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.DesugarMethodAttribute; +import com.google.devtools.build.android.desugar.langmodel.DesugarMethodInfo; +import com.google.devtools.build.android.desugar.langmodel.LangModelHelper; +import com.google.devtools.build.android.desugar.langmodel.MemberUseKind; +import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import com.google.devtools.build.android.desugar.langmodel.SwitchableTypeMapper; +import com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchy; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.MethodRemapper; +import org.objectweb.asm.tree.MethodNode; + +/** + * Desugars the bytecode instructions that interacts with desugar-shadowed APIs, which is a method + * with desugar-shadowed types, e.g. {@code java.time.MonthDay}. + */ +public final class ShadowedApiInvocationSite extends ClassVisitor { + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private static final SwitchableTypeMapper + immutableLabelApplicator = + new SwitchableTypeMapper<>(IN_PROCESS_LABEL_STRIPPER.andThen(IMMUTABLE_LABEL_ATTACHER)); + + /** An evolving record that collects the adapter method requests from invocation sites. */ + private final InvocationSiteTransformationRecordBuilder invocationSiteRecord; + + private final BootClassPathDigest bootClassPathDigest; + private final TypeHierarchy typeHierarchy; + private final ClassAttributeRecord classAttributeRecord; + + private int classAccess; + private ClassName className; + private ImmutableSet desugarIgnoredMethods; + + public ShadowedApiInvocationSite( + ClassVisitor classVisitor, + InvocationSiteTransformationRecordBuilder invocationSiteRecord, + BootClassPathDigest bootClassPathDigest, + ClassAttributeRecord classAttributeRecord, + TypeHierarchy typeHierarchy) { + super(Opcodes.ASM9, classVisitor); + this.invocationSiteRecord = invocationSiteRecord; + this.bootClassPathDigest = bootClassPathDigest; + this.classAttributeRecord = classAttributeRecord; + this.typeHierarchy = typeHierarchy; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + this.classAccess = access; + this.className = ClassName.create(name); + this.desugarIgnoredMethods = + classAttributeRecord.hasAttributeRecordFor(className) + ? classAttributeRecord.getDesugarIgnoredMethods(className) + : ImmutableSet.of(); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodDeclInfo verbatimMethod = + MethodDeclInfo.create( + MethodKey.create(className, name, descriptor), + classAccess, + access, + signature, + exceptions) + .acceptTypeMapper(IN_PROCESS_LABEL_STRIPPER); + if (desugarIgnoredMethods.contains(verbatimMethod.methodKey())) { + MethodVisitor bridgeMethodVisitor = + verbatimMethod.acceptTypeMapper(IMMUTABLE_LABEL_ATTACHER).accept(cv); + return new MethodRemapper( + bridgeMethodVisitor, IN_PROCESS_LABEL_STRIPPER.andThen(IMMUTABLE_LABEL_ATTACHER)); + } + if (ShadowedApiAdapterHelper.shouldEmitApiOverridingBridge( + verbatimMethod, typeHierarchy, bootClassPathDigest)) { + MethodNode bridgeMethodNode = new MethodNode(); + + bridgeMethodNode.visitAttribute( + new DesugarMethodAttribute( + DesugarMethodInfo.newBuilder() + .setDesugarToolIgnore(true) + .setSyntheticReason(DesugarMethodInfo.SyntheticReason.OVERRIDING_BRIDGE) + .build())); + + bridgeMethodNode.visitCode(); + + int slotOffset = 0; + bridgeMethodNode.visitVarInsn(Opcodes.ALOAD, slotOffset++); + for (Type argType : verbatimMethod.argumentTypes()) { + ClassName argTypeName = ClassName.create(argType); + bridgeMethodNode.visitVarInsn(argType.getOpcode(Opcodes.ILOAD), slotOffset); + if (argTypeName.isDesugarShadowedType()) { + MethodInvocationSite conversion = + ShadowedApiAdapterHelper.shadowedToMirroredTypeConversionSite(argTypeName); + conversion.accept(bridgeMethodNode); + } + slotOffset += argType.getSize(); + } + + // revisit + MethodInvocationSite shadowedInvocation = + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKEVIRTUAL) + .setMethod(verbatimMethod.methodKey()) + .setIsInterface(false) + .build(); + MethodInvocationSite mirroredInvocationSite = + shadowedInvocation.acceptTypeMapper(SHADOWED_TO_MIRRORED_TYPE_MAPPER); + mirroredInvocationSite.accept(bridgeMethodNode); + + // TODO(deltazulu): Refine forward / backward conversions. + invocationSiteRecord.addInlineConversion(shadowedInvocation); + + ClassName adapterReturnTypeName = verbatimMethod.returnTypeName(); + if (adapterReturnTypeName.isDesugarShadowedType()) { + MethodInvocationSite conversion = + ShadowedApiAdapterHelper.mirroredToShadowedTypeConversionSite( + adapterReturnTypeName.shadowedToMirrored()); + conversion.accept(bridgeMethodNode); + } + + bridgeMethodNode.visitInsn(verbatimMethod.returnType().getOpcode(Opcodes.IRETURN)); + + bridgeMethodNode.visitMaxs(slotOffset, slotOffset); + bridgeMethodNode.visitEnd(); + + MethodVisitor bridgeMethodVisitor = + verbatimMethod.toBuilder() + .setMemberAccess(verbatimMethod.memberAccess() | Opcodes.ACC_SYNTHETIC) + .build() + .acceptTypeMapper(IMMUTABLE_LABEL_ATTACHER) + .accept(cv); + + MethodRemapper methodRemapper = + new MethodRemapper( + bridgeMethodVisitor, IN_PROCESS_LABEL_STRIPPER.andThen(IMMUTABLE_LABEL_ATTACHER)); + + bridgeMethodNode.accept(methodRemapper); + } + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return mv == null + ? null + : new ShadowedApiInvocationSiteMethodVisitor( + api, mv, verbatimMethod, invocationSiteRecord, typeHierarchy, bootClassPathDigest); + } + + /** Desugars instructions for the enclosing class visitor. */ + private static class ShadowedApiInvocationSiteMethodVisitor extends MethodRemapper { + + private static final String BEGIN_TAG = "BEGIN:"; + private static final String END_TAG = "END:"; + + private final MethodDeclInfo enclosingMethod; + private final InvocationSiteTransformationRecordBuilder invocationSiteRecord; + private final TypeHierarchy typeHierarchy; + private final BootClassPathDigest bootClassPathDigest; + + /** + * The transformation-in-process invocation site while visiting. The value can be {@code null} + * if no in-line type conversion is in process. + */ + private InvocationSiteTransformationReason inProcessTransformation; + + private ShadowedApiInvocationSiteMethodVisitor( + int api, + MethodVisitor methodVisitor, + MethodDeclInfo enclosingMethod, + InvocationSiteTransformationRecordBuilder invocationSiteRecord, + TypeHierarchy typeHierarchy, + BootClassPathDigest bootClassPathDigest) { + super(api, methodVisitor, immutableLabelApplicator); + this.enclosingMethod = enclosingMethod; + this.invocationSiteRecord = invocationSiteRecord; + this.typeHierarchy = typeHierarchy; + this.bootClassPathDigest = bootClassPathDigest; + } + + @Override + public void visitLdcInsn(Object value) { + if (value instanceof String) { + String paramTypeConversionTag = (String) value; + if (paramTypeConversionTag.startsWith(BEGIN_TAG + INLINE_PARAM_TYPE_CONVERSION)) { + checkState(inProcessTransformation == null); + inProcessTransformation = + InvocationSiteTransformationReason.decode( + paramTypeConversionTag.substring(BEGIN_TAG.length())); + immutableLabelApplicator.turnOn(inProcessTransformation); + } else if (paramTypeConversionTag.startsWith(END_TAG + INLINE_PARAM_TYPE_CONVERSION)) { + checkState(inProcessTransformation != null); + InvocationSiteTransformationReason reasonToClose = + InvocationSiteTransformationReason.decode( + paramTypeConversionTag.substring(END_TAG.length())); + immutableLabelApplicator.turnOff(reasonToClose); + inProcessTransformation = null; + } + } + super.visitLdcInsn(value); + } + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String descriptor, boolean isInterface) { + MethodInvocationSite verbatimInvocationSite = + MethodInvocationSite.create(opcode, owner, name, descriptor, isInterface) + .acceptTypeMapper(IN_PROCESS_LABEL_STRIPPER); + + if (inProcessTransformation != null) { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + return; + } + + if (shouldUseInlineTypeConversion( + verbatimInvocationSite, typeHierarchy, bootClassPathDigest, enclosingMethod)) { + logger.atInfo().log( + "----> Inline Type Conversion performed for %s", verbatimInvocationSite); + InvocationSiteTransformationReason transformationReason = + InvocationSiteTransformationReason.create( + INLINE_PARAM_TYPE_CONVERSION, verbatimInvocationSite.method()); + LangModelHelper.mapOperandStackValues( + this, + ImmutableList.of(ShadowedApiAdapterHelper::anyMirroredToBuiltinTypeConversion), + SHADOWED_TO_MIRRORED_TYPE_MAPPER.map(verbatimInvocationSite.argumentTypeNames()), + verbatimInvocationSite.argumentTypeNames(), + /* beginningMarker= */ BEGIN_TAG + transformationReason.encode()); + + verbatimInvocationSite.accept(this); + + ClassName verbatimReturnTypeName = verbatimInvocationSite.returnTypeName(); + if (verbatimReturnTypeName.isDesugarShadowedType()) { + MethodInvocationSite conversion = + ShadowedApiAdapterHelper.shadowedToMirroredTypeConversionSite(verbatimReturnTypeName); + conversion.accept(this); + } + visitLdcInsn(END_TAG + transformationReason.encode()); + visitInsn(Opcodes.POP); + invocationSiteRecord.addInlineConversion(verbatimInvocationSite); + return; + } + + if (shouldUseApiTypeAdapter(verbatimInvocationSite, bootClassPathDigest)) { + checkState(!immutableLabelApplicator.isSwitchOn()); + MethodInvocationSite adapterSite = + ShadowedApiAdapterHelper.getAdapterInvocationSite(verbatimInvocationSite); + invocationSiteRecord.addAdapterReplacement(verbatimInvocationSite); + adapterSite.accept(this); + return; + } + + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } + + /** Strips out immutable labels at the end of desugar pipeline. */ + public static class ImmutableLabelRemover extends ClassRemapper { + public ImmutableLabelRemover(ClassVisitor cv) { + super(cv, ClassName.IMMUTABLE_LABEL_STRIPPER); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD new file mode 100644 index 00000000000000..addc876e59a5e1 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD @@ -0,0 +1,28 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +java_library( + name = "covariantreturn", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverter.java new file mode 100644 index 00000000000000..ea187b01e49567 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverter.java @@ -0,0 +1,133 @@ +/* + * Copyright 2020 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.covariantreturn; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import com.google.devtools.build.android.desugar.langmodel.TypeMapper; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * A bytecode converter that supports to use covariant return types in the NIO buffer hierarchy. + * + * @see https://bugs.openjdk.java.net/browse/JDK-4774077. + */ +public final class NioBufferRefConverter extends ClassVisitor { + + /** The inheritance hierarchy root of Java NIO buffer family. */ + private static final ClassName NIO_BUFFER_BASE = ClassName.create("java/nio/Buffer"); + + /** All overloading methods in {@link java.nio.Buffer} with covariant return type. */ + private static final ImmutableList BASE_METHODS_WITH_COVARIANT_RETURN_TYPES = + ImmutableList.of( + MethodKey.create(NIO_BUFFER_BASE, "position", "(I)Ljava/nio/Buffer;"), + MethodKey.create(NIO_BUFFER_BASE, "limit", "(I)Ljava/nio/Buffer;"), + MethodKey.create(NIO_BUFFER_BASE, "mark", "()Ljava/nio/Buffer;"), + MethodKey.create(NIO_BUFFER_BASE, "reset", "()Ljava/nio/Buffer;"), + MethodKey.create(NIO_BUFFER_BASE, "clear", "()Ljava/nio/Buffer;"), + MethodKey.create(NIO_BUFFER_BASE, "flip", "()Ljava/nio/Buffer;"), + MethodKey.create(NIO_BUFFER_BASE, "rewind", "()Ljava/nio/Buffer;")); + + /** All public type-specific NIO buffer classes derived from {@link java.nio.Buffer}. */ + private static final ImmutableSet TYPE_SPECIFIC_NIO_BUFFERS = + ImmutableSet.of( + ClassName.create("java/nio/IntBuffer"), + ClassName.create("java/nio/CharBuffer"), + ClassName.create("java/nio/FloatBuffer"), + ClassName.create("java/nio/DoubleBuffer"), + ClassName.create("java/nio/ShortBuffer"), + ClassName.create("java/nio/LongBuffer"), + ClassName.create("java/nio/ByteBuffer")); + + /** Used to find the replacement method from the original method invocation specification. */ + private final ImmutableMap methodInvocationMappings; + + /** The public factory API for this class. */ + public static NioBufferRefConverter create( + ClassVisitor classVisitor, TypeMapper corePackagePrefixer) { + return new NioBufferRefConverter( + classVisitor, corePackagePrefixer.map(getMethodInvocationMappings())); + } + + private NioBufferRefConverter( + ClassVisitor classVisitor, ImmutableMap methodInvocationMappings) { + super(Opcodes.ASM9, classVisitor); + this.methodInvocationMappings = methodInvocationMappings; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return mv == null ? null : new NioBufferMethodVisitor(api, mv, methodInvocationMappings); + } + + /** Computes methods in Java NIO buffer family that are subject to invocation conversion. */ + private static ImmutableMap getMethodInvocationMappings() { + ImmutableMap.Builder methodMappings = ImmutableMap.builder(); + for (ClassName typeSpecificNioBuffer : TYPE_SPECIFIC_NIO_BUFFERS) { + for (MethodKey baseMethod : BASE_METHODS_WITH_COVARIANT_RETURN_TYPES) { + methodMappings.put( + MethodKey.create( + typeSpecificNioBuffer, + baseMethod.name(), + Type.getMethodDescriptor( + typeSpecificNioBuffer.toAsmObjectType(), baseMethod.getArgumentTypeArray())), + MethodKey.create(typeSpecificNioBuffer, baseMethod.name(), baseMethod.descriptor())); + } + } + return methodMappings.buildOrThrow(); + } + + private static class NioBufferMethodVisitor extends MethodVisitor { + + private final ImmutableMap methodInvocationMappings; + + NioBufferMethodVisitor( + int api, + MethodVisitor methodVisitor, + ImmutableMap methodInvocationMappings) { + super(api, methodVisitor); + this.methodInvocationMappings = methodInvocationMappings; + } + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String descriptor, boolean isInterface) { + MethodKey methodKey = MethodKey.create(ClassName.create(owner), name, descriptor); + if (methodInvocationMappings.containsKey(methodKey)) { + MethodKey mappedMethodKey = methodInvocationMappings.get(methodKey); + super.visitMethodInsn( + opcode, + mappedMethodKey.ownerName(), + mappedMethodKey.name(), + mappedMethodKey.descriptor(), + isInterface); + super.visitTypeInsn(Opcodes.CHECKCAST, owner); + return; + } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/desugar.sh b/src/tools/android/java/com/google/devtools/build/android/desugar/desugar.sh new file mode 100755 index 00000000000000..0f4bd29f99d042 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/desugar.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Copyright 2021 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. +# +# A wrapper around the desugar binary that sets +# jdk.internal.lambda.dumpProxyClasses. + +# exit on errors and uninitialized variables + +# --- begin runfiles.bash initialization v2 --- +# Copy-pasted from the Bazel Bash runfiles library v2. +set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v2 --- + +readonly TMPDIR="$(mktemp -d)" +trap "rm -rf ${TMPDIR}" EXIT +$(rlocation io_bazel/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar_java) \ + "--jvm_flag=-Djdk.internal.lambda.dumpProxyClasses=${TMPDIR}" "$@" diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/BUILD new file mode 100644 index 00000000000000..272906face4be3 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/BUILD @@ -0,0 +1,29 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +java_library( + name = "nest", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/FieldAccessBridgeEmitter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/FieldAccessBridgeEmitter.java new file mode 100644 index 00000000000000..cbc1978d5c1429 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/FieldAccessBridgeEmitter.java @@ -0,0 +1,134 @@ +/* + * Copyright 2019 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.nest; + +import static org.objectweb.asm.Opcodes.ACC_STATIC; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; +import static org.objectweb.asm.Opcodes.GETSTATIC; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.langmodel.FieldInstrVisitor; +import com.google.devtools.build.android.desugar.langmodel.FieldKey; +import com.google.devtools.build.android.desugar.langmodel.LangModelHelper; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** A visitor class that emits bridge methods for private field accesses. */ +final class FieldAccessBridgeEmitter + implements FieldInstrVisitor { + + /** Emits a bridge method for a field with a {@link Opcodes.GETSTATIC} access. */ + @Override + public MethodVisitor visitGetStatic(FieldKey fieldKey, ClassVisitor cv) { + MethodKey bridgeMethodKey = fieldKey.bridgeOfStaticRead(); + + MethodVisitor mv = + cv.visitMethod( + ACC_SYNTHETIC | ACC_STATIC, + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + /* signature= */ null, + /* exceptions= */ null); + + mv.visitFieldInsn(GETSTATIC, fieldKey.ownerName(), fieldKey.name(), fieldKey.descriptor()); + Type fieldType = fieldKey.getFieldType(); + mv.visitInsn(fieldType.getOpcode(Opcodes.IRETURN)); + int fieldTypeSize = fieldType.getSize(); + mv.visitMaxs(fieldTypeSize, fieldTypeSize); + mv.visitEnd(); + return mv; + } + + /** Emits a bridge method for a field with a {@link Opcodes.PUTSTATIC} access. */ + @Override + public MethodVisitor visitPutStatic(FieldKey fieldKey, ClassVisitor cv) { + MethodKey bridgeMethodKey = fieldKey.bridgeOfStaticWrite(); + MethodVisitor mv = + cv.visitMethod( + ACC_SYNTHETIC | ACC_STATIC, + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + /* signature= */ null, + /* exceptions= */ null); + mv.visitCode(); + Type fieldType = fieldKey.getFieldType(); + mv.visitVarInsn(fieldType.getOpcode(Opcodes.ILOAD), 0); + mv.visitInsn( + LangModelHelper.getTypeSizeAlignedDupOpcode(ImmutableList.of(fieldKey.getFieldType()))); + mv.visitFieldInsn( + Opcodes.PUTSTATIC, fieldKey.ownerName(), fieldKey.name(), fieldKey.descriptor()); + mv.visitInsn(fieldType.getOpcode(Opcodes.IRETURN)); + int fieldTypeSize = fieldType.getSize(); + mv.visitMaxs(fieldTypeSize, fieldTypeSize); + mv.visitEnd(); + return mv; + } + + /** Emits a bridge method for a field with a {@link Opcodes.PUTFIELD} access. */ + @Override + public MethodVisitor visitGetField(FieldKey fieldKey, ClassVisitor cv) { + MethodKey bridgeMethodKey = fieldKey.bridgeOfInstanceRead(); + MethodVisitor mv = + cv.visitMethod( + ACC_SYNTHETIC | ACC_STATIC, + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + /* signature= */ null, + /* exceptions= */ null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn( + Opcodes.GETFIELD, fieldKey.ownerName(), fieldKey.name(), fieldKey.descriptor()); + Type fieldType = fieldKey.getFieldType(); + mv.visitInsn(fieldType.getOpcode(Opcodes.IRETURN)); + int fieldTypeSize = fieldType.getSize(); + mv.visitMaxs(fieldTypeSize, fieldTypeSize); + mv.visitEnd(); + return mv; + } + + /** Emits a bridge method for a field with a {@link Opcodes.PUTFIELD} access. */ + @Override + public MethodVisitor visitPutField(FieldKey fieldKey, ClassVisitor cv) { + MethodKey bridgeMethodKey = fieldKey.bridgeOfInstanceWrite(); + MethodVisitor mv = + cv.visitMethod( + ACC_SYNTHETIC | ACC_STATIC, + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + /* signature= */ null, + /* exceptions= */ null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + Type fieldType = fieldKey.getFieldType(); + mv.visitVarInsn(fieldType.getOpcode(Opcodes.ILOAD), 1); + mv.visitInsn( + LangModelHelper.getTypeSizeAlignedDupOpcode( + ImmutableList.of(fieldKey.getFieldType()), + ImmutableList.of(Type.getType(Object.class)))); + mv.visitFieldInsn( + Opcodes.PUTFIELD, fieldKey.ownerName(), fieldKey.name(), fieldKey.descriptor()); + mv.visitInsn(fieldType.getOpcode(Opcodes.IRETURN)); + int fieldTypeSize = fieldType.getSize(); + mv.visitMaxs(fieldTypeSize, fieldTypeSize); + mv.visitEnd(); + return mv; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/MethodAccessorEmitter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/MethodAccessorEmitter.java new file mode 100644 index 00000000000000..4ff019debf66e4 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/MethodAccessorEmitter.java @@ -0,0 +1,228 @@ +/* + * Copyright 2019 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.nest; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo; +import com.google.devtools.build.android.desugar.langmodel.MethodDeclVisitor; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** + * A visitor class that generation of accessor methods, including bridge method generation for class + * and private interface method re-writing. + */ +final class MethodAccessorEmitter + implements MethodDeclVisitor { + + private final NestDigest nestDigest; + + MethodAccessorEmitter(NestDigest nestDigest) { + this.nestDigest = nestDigest; + } + + /** + * Emits a synthetic overloaded constructor which delegates the construction logic to the source + * constructor. For example, + * + *
    
    +   *   class Foo {
    +   *     private Foo(A a) {...}
    +   *
    +   *     // Synthetic overloaded constructor
    +   *     Foo(A a, NestCC var) {
    +   *       this(a);
    +   *     }
    +   *   }
    +   * 
    + */ + @Override + public MethodVisitor visitClassConstructor(MethodDeclInfo methodDeclInfo, ClassVisitor cv) { + ClassName nestCompanion = nestDigest.nestCompanion(methodDeclInfo.owner()); + MethodDeclInfo constructorBridge = methodDeclInfo.bridgeOfConstructor(nestCompanion); + MethodVisitor mv = constructorBridge.accept(cv); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + + ImmutableList constructorBridgeArgTypes = constructorBridge.argumentTypes(); + // Exclude last placeholder element loading. + for (int i = 0, slotOffset = 1; i < constructorBridgeArgTypes.size() - 1; i++) { + mv.visitVarInsn(constructorBridgeArgTypes.get(i).getOpcode(Opcodes.ILOAD), slotOffset); + slotOffset += constructorBridgeArgTypes.get(i).getSize(); + } + mv.visitMethodInsn( + Opcodes.INVOKESPECIAL, + methodDeclInfo.ownerName(), + methodDeclInfo.name(), + methodDeclInfo.descriptor(), + /* isInterface= */ false); + mv.visitInsn(Opcodes.RETURN); + int slot = 0; + for (Type bridgeConstructorArgType : constructorBridgeArgTypes) { + slot += bridgeConstructorArgType.getSize(); + } + mv.visitMaxs(slot, slot); + mv.visitEnd(); + return mv; + } + + /** + * Emits a bridge method for a static method in a class. For example, + * + *
    
    +   *   class Foo {
    +   *     private static X execute(A a) {...}
    +   *
    +   *     // Synthetic bridge method for a static method.
    +   *     static X execute$bridge(A a) {
    +   *       return execute(a);
    +   *     }
    +   *   }
    +   * 
    + */ + @Override + public MethodVisitor visitClassStaticMethod(MethodDeclInfo methodDeclInfo, ClassVisitor cv) { + MethodDeclInfo bridgeMethod = methodDeclInfo.bridgeOfClassStaticMethod(); + MethodVisitor mv = bridgeMethod.accept(cv); + mv.visitCode(); + int slotOffset = 0; + for (Type argType : bridgeMethod.argumentTypes()) { + mv.visitVarInsn(argType.getOpcode(Opcodes.ILOAD), slotOffset); + slotOffset += argType.getSize(); + } + + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + methodDeclInfo.ownerName(), + methodDeclInfo.name(), + methodDeclInfo.descriptor(), + /* isInterface= */ false); + mv.visitInsn(bridgeMethod.returnType().getOpcode(Opcodes.IRETURN)); + mv.visitMaxs(slotOffset, slotOffset); + mv.visitEnd(); + return mv; + } + + /** + * Emits a bridge method for an instance method in a class. For example, + * + *
    
    +   *   class Foo {
    +   *     private X execute(A a) {...}
    +   *
    +   *     // Synthetic bridge method for a static method.
    +   *     static X execute$bridge(Foo foo, A a) {
    +   *       return foo.execute(a);
    +   *     }
    +   *   }
    +   * 
    + */ + @Override + public MethodVisitor visitClassInstanceMethod(MethodDeclInfo methodDeclInfo, ClassVisitor cv) { + MethodDeclInfo bridgeMethod = methodDeclInfo.bridgeOfClassInstanceMethod(); + MethodVisitor mv = bridgeMethod.accept(cv); + mv.visitCode(); + int slotOffset = 0; + for (Type argType : bridgeMethod.argumentTypes()) { + mv.visitVarInsn(argType.getOpcode(Opcodes.ILOAD), slotOffset); + slotOffset += argType.getSize(); + } + + mv.visitMethodInsn( + Opcodes.INVOKESPECIAL, + methodDeclInfo.ownerName(), + methodDeclInfo.name(), + methodDeclInfo.descriptor(), + /* isInterface= */ false); + mv.visitInsn(bridgeMethod.returnType().getOpcode(Opcodes.IRETURN)); + mv.visitMaxs(slotOffset, slotOffset); + mv.visitEnd(); + return mv; + } + + /** + * Rewrites the modifiers, name and descriptor of a declared interface static method. + * + *

    For example, + * + *

    The visitor converts + * + *

    
    +   *   package path.a.b;
    +   *
    +   *   interface Foo {
    +   *     private static X execute(A a) {...}
    +   *   }
    +   * 
    + * + * to + * + *
    
    +   *   package path.a.b;
    +   *
    +   *   interface Foo {
    +   *     // The package path is mangled to the new method name to avoid name clashing for
    +   *     // interfaces with inheritance.
    +   *     static X execute(A a) {...}
    +   *   }
    +   * 
    + * + * Note: The desugared method will be subsequently moved to the interface's companion class by + * {@link com.google.devtools.build.android.desugar.InterfaceDesugaring}. + */ + @Override + public MethodVisitor visitInterfaceStaticMethod(MethodDeclInfo methodDeclInfo, ClassVisitor cv) { + return methodDeclInfo.substituteOfInterfaceStaticMethod().accept(cv); + } + + /** + * Rewrites the modifiers and header of a declared interface instance method. For example, + * + *

    The visitor converts + * + *

    
    +   *   package path.a.b;
    +   *
    +   *   interface Foo {
    +   *     private X execute(A a) {...}
    +   *   }
    +   * 
    + * + * to + * + *
    
    +   *   package path.a.b;
    +   *
    +   *   interface Foo {
    +   *     // The package path is mangled to the new method name to avoid name clashing for
    +   *     // interfaces with inheritance.
    +   *     static X path_a_b_execute(Foo foo, A a) {...}
    +   *   }
    +   * 
    + * + * Note: The desugared method will be subsequently moved to the interface's companion class by + * {@link com.google.devtools.build.android.desugar.InterfaceDesugaring}. + */ + @Override + public MethodVisitor visitInterfaceInstanceMethod( + MethodDeclInfo methodDeclInfo, ClassVisitor cv) { + return methodDeclInfo.substituteOfInterfaceInstanceMethod().accept(cv); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestAnalyzer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestAnalyzer.java new file mode 100644 index 00000000000000..cad8bbaad959c3 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestAnalyzer.java @@ -0,0 +1,36 @@ +// Copyright 2019 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.nest; + + +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord; + +/** + * An analyzer that performs nest-based analysis and save the states to {@link ClassMemberRecord} + * and generated {@link NestDigest}. + */ +public class NestAnalyzer { + + private NestAnalyzer() {} + + public static NestDigest digest( + ClassAttributeRecord classAttributeRecord, ClassMemberRecord classMemberRecord) { + return NestDigest.builder() + .setClassMemberRecord(classMemberRecord.filterUsedMemberWithTrackedDeclaration()) + .setClassAttributeRecord(classAttributeRecord) + .build(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestBridgeRefConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestBridgeRefConverter.java new file mode 100644 index 00000000000000..79f506f3b339f7 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestBridgeRefConverter.java @@ -0,0 +1,249 @@ +// Copyright 2019 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.nest; + +import static com.google.devtools.build.android.desugar.langmodel.LangModelHelper.isCrossMateRefInNest; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.FieldInstrVisitor; +import com.google.devtools.build.android.desugar.langmodel.FieldKey; +import com.google.devtools.build.android.desugar.langmodel.LangModelHelper; +import com.google.devtools.build.android.desugar.langmodel.MemberUseKind; +import com.google.devtools.build.android.desugar.langmodel.MethodInstrVisitor; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import org.objectweb.asm.Opcodes; +import javax.annotation.Nullable; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** A visitor that convert direct access to field/method with access through bridges. */ +public final class NestBridgeRefConverter extends MethodVisitor { + + private final MethodKey enclosingMethodKey; + private final NestDigest nestDigest; + private final FieldAccessToBridgeRedirector directFieldAccessReplacer; + private final MethodToBridgeRedirector methodToBridgeRedirector; + + NestBridgeRefConverter( + @Nullable MethodVisitor methodVisitor, MethodKey methodKey, NestDigest nestDigest) { + super(Opcodes.ASM9, methodVisitor); + this.enclosingMethodKey = methodKey; + this.nestDigest = nestDigest; + + directFieldAccessReplacer = new FieldAccessToBridgeRedirector(); + methodToBridgeRedirector = new MethodToBridgeRedirector(nestDigest); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + FieldKey fieldKey = FieldKey.create(ClassName.create(owner), name, descriptor); + MemberUseKind useKind = MemberUseKind.fromValue(opcode); + if (isCrossMateRefInNest(fieldKey, enclosingMethodKey) + && nestDigest.hasAnyUse(fieldKey, useKind)) { + fieldKey.accept(useKind, directFieldAccessReplacer, mv); + return; + } + super.visitFieldInsn(opcode, owner, name, descriptor); + } + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String descriptor, boolean isInterface) { + MethodKey methodKey = MethodKey.create(ClassName.create(owner), name, descriptor); + MemberUseKind useKind = MemberUseKind.fromValue(opcode); + + // Desugar invokevirtual on private instance methods within the same class. + if (!isInterface + && useKind == MemberUseKind.INVOKEVIRTUAL + && methodKey.owner().equals(enclosingMethodKey.owner()) + && nestDigest.isPrivateInstanceMethod(methodKey)) { + super.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, descriptor, isInterface); + return; + } + + if ((isInterface || isCrossMateRefInNest(methodKey, enclosingMethodKey)) + && nestDigest.hasAnyUse(methodKey, useKind)) { + methodKey.accept(useKind, isInterface, methodToBridgeRedirector, mv); + return; + } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + /** A visitor that re-directs the invocation of a method to that of the method's bridge. */ + static class MethodToBridgeRedirector + implements MethodInstrVisitor { + + private final NestDigest nestDigest; + + MethodToBridgeRedirector(NestDigest nestDigest) { + this.nestDigest = nestDigest; + } + + @Override + public MethodKey visitInvokeVirtual(MethodKey methodKey, MethodVisitor mv) { + MethodKey bridgeMethodKey = methodKey.bridgeOfClassInstanceMethod(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + bridgeMethodKey.ownerName(), + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + /* isInterface= */ false); + return methodKey; + } + + @Override + public MethodKey visitInvokeSpecial(MethodKey methodKey, MethodVisitor mv) { + MethodKey bridgeMethodKey = methodKey.bridgeOfClassInstanceMethod(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + bridgeMethodKey.ownerName(), + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + /* isInterface= */ false); + return methodKey; + } + + @Override + public MethodKey visitConstructorInvokeSpecial(MethodKey methodKey, MethodVisitor mv) { + ClassName nestCompanion = nestDigest.nestCompanion(ClassName.create(methodKey.ownerName())); + MethodKey constructorBridge = methodKey.bridgeOfConstructor(nestCompanion); + mv.visitInsn(Opcodes.ACONST_NULL); + mv.visitMethodInsn( + Opcodes.INVOKESPECIAL, + constructorBridge.ownerName(), + constructorBridge.name(), + constructorBridge.descriptor(), + /* isInterface= */ false); + return methodKey; + } + + @Override + public MethodKey visitInterfaceInvokeSpecial(MethodKey methodKey, MethodVisitor mv) { + MethodKey methodBridge = methodKey.substituteOfInterfaceInstanceMethod(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + methodBridge.ownerName(), + methodBridge.name(), + methodBridge.descriptor(), + /* isInterface= */ true); + return methodKey; + } + + @Override + public MethodKey visitInvokeStatic(MethodKey methodKey, MethodVisitor mv) { + MethodKey bridgeMethodKey = methodKey.bridgeOfClassStaticMethod(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + bridgeMethodKey.ownerName(), + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + /* isInterface= */ false); + return methodKey; + } + + @Override + public MethodKey visitInterfaceInvokeStatic(MethodKey methodKey, MethodVisitor mv) { + final MethodKey methodBridge = methodKey.substituteOfInterfaceStaticMethod(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + methodBridge.ownerName(), + methodBridge.name(), + methodBridge.descriptor(), + /* isInterface= */ true); + return methodKey; + } + + @Override + public MethodKey visitInvokeInterface(MethodKey methodKey, MethodVisitor mv) { + final MethodKey methodBridge = methodKey.substituteOfInterfaceInstanceMethod(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + methodBridge.ownerName(), + methodBridge.name(), + methodBridge.descriptor(), + /* isInterface= */ true); + return methodKey; + } + + @Override + public MethodKey visitInvokeDynamic(MethodKey methodKey, MethodVisitor mv) { + throw new UnsupportedOperationException(); + } + } + + /** A visitor that re-directs field access with bridge method invocation. */ + static class FieldAccessToBridgeRedirector + implements FieldInstrVisitor { + + @Override + public MethodKey visitGetStatic(FieldKey fieldKey, MethodVisitor mv) { + MethodKey bridgeMethodKey = fieldKey.bridgeOfStaticRead(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + bridgeMethodKey.ownerName(), + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + false); + return bridgeMethodKey; + } + + @Override + public MethodKey visitPutStatic(FieldKey fieldKey, MethodVisitor mv) { + MethodKey bridgeMethodKey = fieldKey.bridgeOfStaticWrite(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + bridgeMethodKey.ownerName(), + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + false); + // The bridge method for an instance field writer pushes the new field value to its invoker + // operand stack, we emit a pop instruction to be consistent with putfield instruction which + // consumes the updated field value on the operand stack. + mv.visitInsn( + LangModelHelper.getTypeSizeAlignedPopOpcode(ImmutableList.of(fieldKey.getFieldType()))); + return bridgeMethodKey; + } + + @Override + public MethodKey visitGetField(FieldKey fieldKey, MethodVisitor mv) { + MethodKey bridgeMethodKey = fieldKey.bridgeOfInstanceRead(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + bridgeMethodKey.ownerName(), + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + false); + return bridgeMethodKey; + } + + @Override + public MethodKey visitPutField(FieldKey fieldKey, MethodVisitor mv) { + MethodKey bridgeMethodKey = fieldKey.bridgeOfInstanceWrite(); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + bridgeMethodKey.ownerName(), + bridgeMethodKey.name(), + bridgeMethodKey.descriptor(), + /* isInterface= */ false); + // The bridge method for an instance field writer pushes the new field value to its invoker + // operand stack, we emit a pop instruction to be consistent with putfield instruction which + // consumes the updated field value on the operand stack. + mv.visitInsn( + LangModelHelper.getTypeSizeAlignedPopOpcode(ImmutableList.of(fieldKey.getFieldType()))); + return bridgeMethodKey; + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDesugarConstants.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDesugarConstants.java new file mode 100644 index 00000000000000..c1ab4192a80f2b --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDesugarConstants.java @@ -0,0 +1,25 @@ +// Copyright 2019 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.nest; + +import org.objectweb.asm.Opcodes; + +/** Shared constants of Nest-accessed desugar. */ +final class NestDesugarConstants { + + static final int MIN_VERSION = Opcodes.V1_8; + + private NestDesugarConstants() {} +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDesugaring.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDesugaring.java new file mode 100644 index 00000000000000..304a26f701104e --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDesugaring.java @@ -0,0 +1,174 @@ +// Copyright 2019 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.nest; + +import static com.google.devtools.build.android.desugar.langmodel.LangModelConstants.NEST_COMPANION_CLASS_SIMPLE_NAME; +import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; +import static org.objectweb.asm.Opcodes.ACC_INTERFACE; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; + +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.FieldKey; +import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import org.objectweb.asm.Opcodes; +import javax.annotation.Nullable; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * A class visitor that rewrites visited classes and interfaces with nested-based access [JEP 181]. + * For any private class member with access from another class or interface within the same nest, + * The visitor generates visibility-broadened bridge methods and update all call sites. For any + * private interface member within access from another class or interface within the same nest, the + * visitor broadens the visibility of private interface methods, renames these interface members and + * update their call sites. + */ +public final class NestDesugaring extends ClassVisitor { + + private static final int NEST_COMPANION_CLASS_ACCESS_CODE = + Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC; + + private final NestDigest nestDigest; + + private FieldAccessBridgeEmitter fieldAccessBridgeEmitter; + private MethodAccessorEmitter methodAccessorEmitter; + + private ClassName className; + private int classAccess; + @Nullable private ClassVisitor nestCompanionVisitor; + + /** Whether the class being visited is a nest host with with a nest companion class. */ + private boolean isNestHostWithNestCompanion; + + public NestDesugaring(ClassVisitor classVisitor, NestDigest nestDigest) { + super(Opcodes.ASM9, classVisitor); + this.nestDigest = nestDigest; + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + className = ClassName.create(name); + classAccess = access; + nestCompanionVisitor = nestDigest.getCompanionClassWriter(className); + isNestHostWithNestCompanion = + (nestCompanionVisitor != null) && className.equals(nestDigest.nestHost(className).get()); + fieldAccessBridgeEmitter = new FieldAccessBridgeEmitter(); + methodAccessorEmitter = new MethodAccessorEmitter(nestDigest); + super.visit( + Math.min(version, NestDesugarConstants.MIN_VERSION), + access, + name, + signature, + superName, + interfaces); + if (isNestHostWithNestCompanion) { + nestCompanionVisitor.visit( + Math.min(version, NestDesugarConstants.MIN_VERSION), + ACC_SYNTHETIC | ACC_ABSTRACT, + nestDigest.nestCompanion(className).binaryName(), + /* signature= */ null, + /* superName= */ "java/lang/Object", + /* interfaces= */ new String[0]); + } + } + + @Override + public FieldVisitor visitField( + int access, String name, String descriptor, String signature, Object value) { + FieldKey fieldKey = FieldKey.create(className, name, descriptor); + // Generates necessary bridge methods for this field, including field getter methods and field + // setter methods. See FieldAccessBridgeEmitter. + nestDigest + .findAllMemberUseKinds(fieldKey) + .forEach(useKind -> fieldKey.accept(useKind, fieldAccessBridgeEmitter, cv)); + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodKey methodKey = MethodKey.create(className, name, descriptor); + if (nestDigest.hasAnyTrackingReason(methodKey)) { + MethodDeclInfo methodDeclInfo = + MethodDeclInfo.create(methodKey, classAccess, access, signature, exceptions); + // For interfaces, the following .accept converts the original method into a + // visibility-broadened renamed method in-place while preserving the method body + // implementation; for classes, the following .accept generates synthetic bridge methods and + // preserve the original methods. The returned method visitor is subject to further desugar + // operations for interfaces. See {@link MethodAccessorEmitter} + MethodVisitor targetMethodVisitor = methodDeclInfo.accept(methodAccessorEmitter, cv); + + // For classes, we continue to desugar the code of the bridge's originating method. For + // interfaces, we continue to desugar the header-modified methods. The code of the bridge + // method is one simple delegation call to its originating method without any code subject to + // desugar. + MethodVisitor primaryMethodVisitor = + (classAccess & ACC_INTERFACE) != 0 + ? targetMethodVisitor + : super.visitMethod(access, name, descriptor, signature, exceptions); + return new NestBridgeRefConverter(primaryMethodVisitor, methodKey, nestDigest); + } + // In absence of method invocation record, fallback to the delegate method visitor. + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return mv == null ? null : new NestBridgeRefConverter(mv, methodKey, nestDigest); + } + + @Override + public void visitNestHost(String nestHost) { + // Intentionally omit super call to avoid writing NestHost attribute to the class file. + } + + @Override + public void visitNestMember(String nestMember) { + // Intentionally omit super call to avoid writing the NestMembers attribute to the class file. + } + + @Override + public void visitSource(String source, String debug) { + super.visitSource(source, debug); + if (isNestHostWithNestCompanion) { + nestCompanionVisitor.visitSource(source, debug); + } + } + + @Override + public void visitEnd() { + if (isNestHostWithNestCompanion) { + ClassName nestCompanion = nestDigest.nestCompanion(className); + // In the nest companion class, marks its outer class as the nest host. + nestCompanionVisitor.visitInnerClass( + nestCompanion.binaryName(), + className.binaryName(), + NEST_COMPANION_CLASS_SIMPLE_NAME, + NEST_COMPANION_CLASS_ACCESS_CODE); + // In the nest host class, marks the nest companion as one of its inner classes. + cv.visitInnerClass( + nestCompanion.binaryName(), + className.binaryName(), + NEST_COMPANION_CLASS_SIMPLE_NAME, + NEST_COMPANION_CLASS_ACCESS_CODE); + } + super.visitEnd(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDigest.java b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDigest.java new file mode 100644 index 00000000000000..81890483e14b1a --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/nest/NestDigest.java @@ -0,0 +1,216 @@ +// Copyright 2019 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.nest; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.devtools.build.android.desugar.langmodel.LangModelConstants.NEST_COMPANION_CLASS_SIMPLE_NAME; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Streams; +import com.google.devtools.build.android.desugar.io.FileContentProvider; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberKey; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MemberUseKind; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import com.google.devtools.build.android.desugar.langmodel.TypeMappable; +import com.google.devtools.build.android.desugar.langmodel.TypeMapper; +import java.io.ByteArrayInputStream; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import javax.annotation.Nullable; +import org.objectweb.asm.ClassWriter; + +/** Manages the creation and IO stream for nest-companion classes. */ +@AutoValue +public abstract class NestDigest implements TypeMappable { + + public abstract ClassMemberRecord classMemberRecord(); + + public abstract ClassAttributeRecord classAttributeRecord(); + + @Memoized + ImmutableList nestHostsWithCompanion() { + return classMemberRecord().findAllConstructorMemberKeys().stream() + .map( + constructor -> nestHost(constructor.owner(), classAttributeRecord(), ImmutableMap.of())) + .flatMap(Streams::stream) + .distinct() + .collect(toImmutableList()); + } + + @Memoized + ImmutableMap nestCompanionToHostMap() { + return nestHostsWithCompanion().stream() + .collect( + toImmutableMap( + nestHost -> nestHost.innerClass(NEST_COMPANION_CLASS_SIMPLE_NAME), + nestHost -> nestHost)); + } + + /** + * A map from the class binary names of nest hosts to the associated class writer of the nest's + * companion. + */ + @Memoized + ImmutableMap companionWriters() { + return nestHostsWithCompanion().stream() + .collect( + toImmutableMap( + nestHost -> nestHost, nestHost -> new ClassWriter(ClassWriter.COMPUTE_MAXS))); + } + + public static NestDigestBuilder builder() { + return new AutoValue_NestDigest.Builder(); + } + + public boolean hasAnyTrackingReason(ClassMemberKey classMemberKey) { + return classMemberRecord().hasTrackingReason(classMemberKey); + } + + public boolean hasAnyUse(ClassMemberKey classMemberKey, MemberUseKind useKind) { + return findAllMemberUseKinds(classMemberKey).contains(useKind); + } + + public boolean isPrivateInstanceMethod(MethodKey methodKey) { + return classAttributeRecord().getPrivateInstanceMethods(methodKey.owner()).contains(methodKey); + } + + public ImmutableList findAllMemberUseKinds(ClassMemberKey classMemberKey) { + return classMemberRecord().findAllMemberUseKind(classMemberKey); + } + + /** + * The public API that finds the nest host for a given class. It is expected {@link + * #prepareCompanionClasses()} executed before this API is ready. The method returns {@link + * Optional#empty()} if the class is not part of a nest. A generated nest companion class and its + * nest host are considered to be a nest host/member relationship. + */ + public Optional nestHost(ClassName className) { + // Ensures prepareCompanionClasses has been executed. + checkNotNull(companionWriters()); + return nestHost(className, classAttributeRecord(), nestCompanionToHostMap()); + } + + /** + * The internal method finds the nest host for a given class from a class attribute record. The + * method returns {@link * Optional#empty()} if the class is not part of a nest. A generated nest + * companion class and its * nest host are considered to be a nest host/member relationship. + * + *

    In addition to exam the NestHost_attribute from the class file, this method returns the + * class under investigation itself for a class with NestMembers_attribute but without + * NestHost_attribute. + */ + private static Optional nestHost( + ClassName className, + ClassAttributeRecord classAttributeRecord, + Map companionToHostMap) { + if (companionToHostMap.containsKey(className)) { + return Optional.of(companionToHostMap.get(className)); + } + Optional nestHost = classAttributeRecord.getNestHost(className); + if (nestHost.isPresent()) { + return nestHost; + } + Set nestMembers = classAttributeRecord.getNestMembers(className); + if (!nestMembers.isEmpty()) { + return Optional.of(className); + } + return Optional.empty(); + } + + /** + * Returns the internal name of the nest companion class for a given class. + * + *

    e.g. The nest host of a/b/C$D is a/b/C$NestCC + */ + public ClassName nestCompanion(ClassName className) { + return nestHost(className) + .map(nestHost -> nestHost.innerClass(NEST_COMPANION_CLASS_SIMPLE_NAME)) + .orElseThrow( + () -> + new IllegalStateException( + String.format( + "Expected the presence of NestHost attribute of %s to get nest companion.", + className))); + } + + /** + * Gets the class visitor of the affiliated nest host of the given class. E.g For the given class + * com/google/a/b/Delta$Echo, it returns the class visitor of com/google/a/b/Delta$NestCC + */ + @Nullable + public ClassWriter getCompanionClassWriter(ClassName className) { + return nestHost(className).map(nestHost -> companionWriters().get(nestHost)).orElse(null); + } + + /** Gets all nest companion classes required to be generated. */ + public ImmutableList getAllCompanionClassNames() { + return getAllCompanionClasses().stream().map(ClassName::binaryName).collect(toImmutableList()); + } + + public ImmutableList getAllCompanionClasses() { + return companionWriters().keySet().stream().map(this::nestCompanion).collect(toImmutableList()); + } + + /** Gets all nest companion files required to be generated. */ + public ImmutableList> getCompanionFileProviders() { + ImmutableList.Builder> fileContents = + ImmutableList.builder(); + for (ClassName companion : getAllCompanionClasses()) { + fileContents.add( + new FileContentProvider<>( + companion.classFilePathName(), + () -> getByteArrayInputStreamOfCompanionClass(companion))); + } + return fileContents.build(); + } + + private ByteArrayInputStream getByteArrayInputStreamOfCompanionClass(ClassName companion) { + ClassWriter companionClassWriter = getCompanionClassWriter(companion); + companionClassWriter.visitEnd(); + checkNotNull( + companionClassWriter, + "Expected companion class (%s) to be present in (%s)", + companionWriters()); + return new ByteArrayInputStream(companionClassWriter.toByteArray()); + } + + @Override + public NestDigest acceptTypeMapper(TypeMapper typeMapper) { + return NestDigest.builder() + .setClassMemberRecord(classMemberRecord().acceptTypeMapper(typeMapper)) + .setClassAttributeRecord(classAttributeRecord().acceptTypeMapper(typeMapper)) + .build(); + } + + /** The builder class for {@link NestDigest}. */ + @AutoValue.Builder + public abstract static class NestDigestBuilder { + + public abstract NestDigestBuilder setClassMemberRecord(ClassMemberRecord value); + + public abstract NestDigestBuilder setClassAttributeRecord(ClassAttributeRecord value); + + public abstract NestDigest build(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/BUILD new file mode 100644 index 00000000000000..ef2fd488d9fe4e --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/BUILD @@ -0,0 +1,32 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +licenses(["notice"]) # Apache 2.0 + +java_library( + name = "preanalysis", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel:desugar_class_attr_java_proto", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/ClassMetadataCollector.java b/src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/ClassMetadataCollector.java new file mode 100644 index 00000000000000..d8450099c2c5c5 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/ClassMetadataCollector.java @@ -0,0 +1,211 @@ +/* + * Copyright 2020 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.preanalysis; + +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord.ClassAttributeRecordBuilder; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributes; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributes.ClassAttributesBuilder; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord.ClassMemberRecordBuilder; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.DesugarMethodAttribute; +import com.google.devtools.build.android.desugar.langmodel.FieldKey; +import com.google.devtools.build.android.desugar.langmodel.LangModelHelper; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * A visitor that performs pre-transformation phase analysis that collects relevant class file + * information for the next transformation phase. + */ +public final class ClassMetadataCollector extends ClassVisitor { + + /** Class member records from a single input for nest-based access control analysis. */ + private final ClassMemberRecordBuilder nestAnalysisBasedMemberRecord; + + /** + * Record that tracks class-level attributes, such nest members, nest mates and desugar-custom + * attributes. + */ + private final ClassAttributeRecordBuilder classAttributeRecord; + + /** + * An class member record to stage member record candidates, merging into the project-wise member + * record during the {@link #visitEnd()} where eligible conditions are specified. + */ + private final ClassMemberRecordBuilder stagingMemberRecord = ClassMemberRecord.builder(); + + private final ClassAttributesBuilder classAttributesBuilder; + + private ClassName className; + private int classAccessCode; + private boolean isInNest; + + ClassMetadataCollector( + ClassMemberRecordBuilder nestAnalysisBasedMemberRecord, + ClassAttributeRecordBuilder classAttributeRecord) { + super(Opcodes.ASM9); + this.nestAnalysisBasedMemberRecord = nestAnalysisBasedMemberRecord; + this.classAttributeRecord = classAttributeRecord; + this.classAttributesBuilder = ClassAttributes.builder(); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + className = ClassName.create(name); + classAccessCode = access; + classAttributesBuilder.setClassBinaryName(className); + super.visit(Math.min(version, Opcodes.V1_7), access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField( + int access, String name, String descriptor, String signature, Object value) { + if ((access & Opcodes.ACC_PRIVATE) != 0) { + stagingMemberRecord.logMemberDecl( + FieldKey.create(className, name, descriptor), classAccessCode, access); + } + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodKey methodKey = MethodKey.create(className, name, descriptor); + if ((access & Opcodes.ACC_PRIVATE) != 0 && (access & Opcodes.ACC_STATIC) == 0) { + classAttributesBuilder.addPrivateInstanceMethod(methodKey); + } + if ((access & Opcodes.ACC_PRIVATE) != 0 && !isInDeclOmissionList(methodKey)) { + stagingMemberRecord.logMemberDecl(methodKey, classAccessCode, access); + } + return new MethodMetadataCollector( + super.visitMethod(access, name, descriptor, signature, exceptions), + methodKey, + classAttributesBuilder, + stagingMemberRecord); + } + + /** + * The method declaration will be explicitly omitted. + * + *

    TODO(deltazulu): Refine this list and condition check. e.g. check ACC_SYNTHETIC flag. + */ + private static boolean isInDeclOmissionList(MethodKey methodKey) { + return methodKey.name().startsWith("lambda$") // handled by LambdaDesugaring. + || methodKey.name().equals("$deserializeLambda$") // handled by LambdaDesugaring. + || methodKey.name().contains("jacoco$") // handled by InterfaceDesugaring. + || methodKey.name().contains("$jacoco"); // handled by InterfaceDesugaring. + } + + @Override + public void visitNestHost(String nestHost) { + isInNest = true; + classAttributesBuilder.setNestHost(ClassName.create(nestHost)); + super.visitNestHost(nestHost); + } + + @Override + public void visitNestMember(String nestMember) { + isInNest = true; + classAttributesBuilder.addNestMember(ClassName.create(nestMember)); + super.visitNestMember(nestMember); + } + + @Override + public void visitSource(String source, String debug) { + classAttributesBuilder.setSourceFileName(source); + super.visitSource(source, debug); + } + + @Override + public void visitEnd() { + if (isInNest || (classAccessCode & Opcodes.ACC_INTERFACE) != 0) { + nestAnalysisBasedMemberRecord.mergeFrom(stagingMemberRecord.build()); + } + classAttributeRecord.addClassAttributes(classAttributesBuilder.build()); + super.visitEnd(); + } + + /** + * A visitor that collects privately referenced class members (fields/constructors/methods) within + * a nest. + */ + private static class MethodMetadataCollector extends MethodVisitor { + + /** The current enclosing the method. */ + private final MethodKey enclosingMethodKey; + + private final ClassAttributesBuilder classAttributesBuilder; + + /** + * A per-class class member record and will determined to merge or not into the main member + * record at visitEnd of its associated class visitor. + * + *

    @see {@link ClassMetadataCollector#stagingMemberRecord} for more details. + */ + private final ClassMemberRecordBuilder nestAnalysisBasedMemberRecord; + + MethodMetadataCollector( + MethodVisitor methodVisitor, + MethodKey enclosingMethodKey, + ClassAttributesBuilder classAttributesBuilder, + ClassMemberRecordBuilder nestAnalysisBasedMemberRecord) { + super(Opcodes.ASM9, methodVisitor); + this.enclosingMethodKey = enclosingMethodKey; + this.classAttributesBuilder = classAttributesBuilder; + this.nestAnalysisBasedMemberRecord = nestAnalysisBasedMemberRecord; + } + + @Override + public void visitAttribute(Attribute attribute) { + if (attribute instanceof DesugarMethodAttribute) { + classAttributesBuilder.addDesugarIgnoredMethods(enclosingMethodKey); + } + super.visitAttribute(attribute); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + FieldKey memberKey = FieldKey.create(ClassName.create(owner), name, descriptor); + if (LangModelHelper.isCrossMateRefInNest(memberKey, enclosingMethodKey)) { + nestAnalysisBasedMemberRecord.logMemberUse(memberKey, opcode); + } + super.visitFieldInsn(opcode, owner, name, descriptor); + } + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String descriptor, boolean isInterface) { + MethodKey memberKey = MethodKey.create(ClassName.create(owner), name, descriptor); + if (isInterface || LangModelHelper.isCrossMateRefInNest(memberKey, enclosingMethodKey)) { + nestAnalysisBasedMemberRecord.logMemberUse(memberKey, opcode); + } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/InputPreAnalyzer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/InputPreAnalyzer.java new file mode 100644 index 00000000000000..1afd54d896eb3c --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis/InputPreAnalyzer.java @@ -0,0 +1,74 @@ +/* + * Copyright 2020 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.preanalysis; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.io.FileContentProvider; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord.ClassAttributeRecordBuilder; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord.ClassMemberRecordBuilder; +import java.io.IOException; +import java.io.InputStream; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; + +/** + * An analyzer that performs pre-transformation phase analysis and save the analyzed summary to + * corresponding record builders, such as member references and class attributes. + */ +public class InputPreAnalyzer { + + private final ImmutableList> inputFileContents; + private final Attribute[] customAttributes; + + private final ClassMemberRecordBuilder classMemberRecord; + private final ClassAttributeRecordBuilder classAttributeRecord; + + public InputPreAnalyzer( + ImmutableList> inputFileContents, + Attribute[] customAttributes) { + this.inputFileContents = checkNotNull(inputFileContents); + this.customAttributes = customAttributes; + this.classMemberRecord = ClassMemberRecord.builder(); + this.classAttributeRecord = ClassAttributeRecord.builder(); + } + + /** Reads class files and performs class file metadata collection and analysis. */ + public void process() throws IOException { + for (FileContentProvider inputClassFile : inputFileContents) { + if (inputClassFile.isClassFile()) { + try (InputStream inputStream = inputClassFile.get()) { + ClassReader cr = new ClassReader(inputStream); + ClassMetadataCollector cv = + new ClassMetadataCollector(classMemberRecord, classAttributeRecord); + cr.accept(cv, customAttributes, 0); + } + } + } + } + + public ClassMemberRecord getClassMemberRecord() { + return classMemberRecord.build(); + } + + public ClassAttributeRecord getClassAttributeRecord() { + return classAttributeRecord.build(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/BUILD new file mode 100644 index 00000000000000..8ce3e95fadfd01 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/BUILD @@ -0,0 +1,48 @@ +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +proto_library( + name = "retarget_proto", + srcs = ["retarget.proto"], + deps = ["//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel:desugar_class_attr_proto"], +) + +java_proto_library( + name = "retarget_java_proto", + deps = [":retarget_proto"], +) + +filegroup( + name = "retarget_config_textproto", + srcs = ["retarget_config.textproto"], +) + +java_library( + name = "retarget", + srcs = glob(["*.java"]), + resources = [ + ":retarget_config_textproto", + ], + deps = [ + ":retarget_java_proto", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:flogger", + "//third_party:guava", + "//third_party:jsr305", + "//third_party/protobuf:protobuf_java", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/ClassMemberRetargetConfig.java b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/ClassMemberRetargetConfig.java new file mode 100644 index 00000000000000..37190a3aa471ee --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/ClassMemberRetargetConfig.java @@ -0,0 +1,147 @@ +/* + * Copyright 2020 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.retarget; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.protobuf.ExtensionRegistry; +import com.google.protobuf.TextFormat; +import java.io.IOError; +import java.io.IOException; +import java.net.URL; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.annotation.Nullable; + +/** + * The configuration that specifies the class members subject to replacement with a new target class + * member. + */ +@AutoValue +public abstract class ClassMemberRetargetConfig { + + public static final URL DEFAULT_PROTO_URL = + ClassMemberRetargetConfig.class.getResource("retarget_config.textproto"); + + /** The configuration file that defines class member retargeting. */ + abstract ImmutableList invocationReplacementConfigUrls(); + + abstract ImmutableList inProcessReplacements(); + + /** + * The enabled configuration flags for invocation replacements. A replacement takes effect if and + * only if the set intersection of this flag and the {@code range} field of {@code + * MethodInvocations#replacements} is non-empty. + */ + abstract ImmutableSet enabledInvocationReplacementRanges(); + + public static ClassMemberRetargetConfig.Builder builder() { + return new AutoValue_ClassMemberRetargetConfig.Builder(); + } + + @Memoized + MethodInvocations invocationReplacementConfigProto() { + MethodInvocations.Builder invocationReplacementsBuilder = MethodInvocations.newBuilder(); + try { + for (URL url : invocationReplacementConfigUrls()) { + String protoText = Resources.toString(url, UTF_8); + MethodInvocations methodInvocations = + TextFormat.parse( + protoText, ExtensionRegistry.getEmptyRegistry(), MethodInvocations.class); + invocationReplacementsBuilder.mergeFrom(methodInvocations); + } + invocationReplacementsBuilder.addAllReplacements(inProcessReplacements()); + return invocationReplacementsBuilder.build(); + } catch (IOException e) { + throw new IOError(e); + } + } + + /** + * The parsed invocation replacement configuration from {@link #invocationReplacementConfigUrls}. + */ + @Memoized + public ImmutableMap invocationReplacements() { + ImmutableMap.Builder replacementsBuilder = + ImmutableMap.builder(); + for (MethodInvocationReplacement replacement : + invocationReplacementConfigProto().getReplacementsList()) { + MethodInvocationSite invocationSite = MethodInvocationSite.fromProto(replacement.getSource()); + Set replacementRanges = new LinkedHashSet<>(replacement.getRangeList()); + if (replacementRanges.contains(ReplacementRange.ALL) + || replacementRanges.stream().anyMatch(enabledInvocationReplacementRanges()::contains)) { + replacementsBuilder.put( + invocationSite, MethodInvocationSite.fromProto(replacement.getDestination())); + } + } + return replacementsBuilder.buildOrThrow(); + } + + @Nullable + public final MethodInvocationSite findReplacementSite( + MethodInvocationSite verbatimInvocationSite) { + return invocationReplacements().get(verbatimInvocationSite); + } + + /** The builder for {@link ClassMemberRetargetConfig}. */ + @AutoValue.Builder + public abstract static class Builder { + + public abstract ImmutableList.Builder invocationReplacementConfigUrlsBuilder(); + + public abstract ImmutableList.Builder + inProcessReplacementsBuilder(); + + public abstract ImmutableSet.Builder + enabledInvocationReplacementRangesBuilder(); + + @CanIgnoreReturnValue + public Builder addInvocationReplacementConfigUrl(URL value) { + invocationReplacementConfigUrlsBuilder().add(value); + return this; + } + + @CanIgnoreReturnValue + public Builder addInProcessReplacement(MethodInvocationReplacement value) { + inProcessReplacementsBuilder().add(value); + return this; + } + + @CanIgnoreReturnValue + public Builder addEnabledInvocationReplacementRange(ReplacementRange value) { + enabledInvocationReplacementRangesBuilder().add(value); + return this; + } + + @CanIgnoreReturnValue + public Builder addAllEnabledInvocationReplacementRange(Collection value) { + enabledInvocationReplacementRangesBuilder().addAll(value); + return this; + } + + public abstract ClassMemberRetargetConfig build(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/ClassMemberRetargetRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/ClassMemberRetargetRewriter.java new file mode 100644 index 00000000000000..f6cb8f7db8c962 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/ClassMemberRetargetRewriter.java @@ -0,0 +1,137 @@ +/* + * Copyright 2020 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.retarget; + +import static com.google.devtools.build.android.desugar.langmodel.ClassName.IN_PROCESS_LABEL_STRIPPER; + +import com.google.common.collect.ImmutableSet; +import com.google.common.flogger.GoogleLogger; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MemberUseKind; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +/** This class rewrites (or removes) some trivial primitive wrapper methods. */ +public class ClassMemberRetargetRewriter extends ClassVisitor { + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + /** The configuration for invocation retargeting. */ + private final ClassMemberRetargetConfig classMemberRetargetConfig; + + /** + * A collector that gathers runtime library classes referenced by the user and generate code. The + * desugar tool copies these classes into output. + */ + private final ImmutableSet.Builder requiredRuntimeSupportTypes; + + public ClassMemberRetargetRewriter( + ClassVisitor cv, + ClassMemberRetargetConfig classMemberRetargetConfig, + ImmutableSet.Builder requiredRuntimeSupportTypes) { + super(Opcodes.ASM9, cv); + this.classMemberRetargetConfig = classMemberRetargetConfig; + this.requiredRuntimeSupportTypes = requiredRuntimeSupportTypes; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor visitor = super.visitMethod(access, name, desc, signature, exceptions); + return visitor == null ? null : new ClassMemberRetargetMethodVisitor(visitor); + } + + private class ClassMemberRetargetMethodVisitor extends MethodVisitor { + + ClassMemberRetargetMethodVisitor(MethodVisitor visitor) { + super(Opcodes.ASM9, visitor); + } + + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean isInterface) { + MethodInvocationSite verbatimInvocationSite = + MethodInvocationSite.create(opcode, owner, name, desc, isInterface) + .acceptTypeMapper(IN_PROCESS_LABEL_STRIPPER); + if (replaceExactMatchedInvocationSites(verbatimInvocationSite)) { + return; + } + if (replacePatternMatchedInvocationSites(verbatimInvocationSite)) { + return; + } + super.visitMethodInsn(opcode, owner, name, desc, isInterface); + } + + private boolean replaceExactMatchedInvocationSites( + MethodInvocationSite verbatimInvocationSite) { + MethodInvocationSite replacementSite = + classMemberRetargetConfig.findReplacementSite(verbatimInvocationSite); + if (replacementSite != null) { + if (replacementSite.name().equals("identityAsHashCode")) { + return true; + } + ClassName successor = replacementSite.owner(); + if (successor.isInDesugarRuntimeLibrary()) { + requiredRuntimeSupportTypes.add(successor); + } + replacementSite.accept(mv); // (b/147139686) + return true; + } + return false; + } + + private boolean replacePatternMatchedInvocationSites( + MethodInvocationSite verbatimInvocationSite) { + MethodInvocationSite invocationSiteOwnerAndNameRepresentative = + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.UNKNOWN) + .setMethod( + MethodKey.create( + verbatimInvocationSite.owner(), verbatimInvocationSite.name(), "")) + .setIsInterface(false) + .build(); + MethodInvocationSite replacementSiteOwnerAndNameRepresentative = + classMemberRetargetConfig.findReplacementSite(invocationSiteOwnerAndNameRepresentative); + if (replacementSiteOwnerAndNameRepresentative != null) { + // The invocation site successor under pattern-matching mode is a static invocation. + MethodInvocationSite replacementSite = + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKESTATIC) + .setMethod( + MethodKey.create( + replacementSiteOwnerAndNameRepresentative.owner(), + replacementSiteOwnerAndNameRepresentative.name(), + verbatimInvocationSite.isStaticInvocation() + ? verbatimInvocationSite.descriptor() + : verbatimInvocationSite.method().instanceMethodToStaticDescriptor())) + .setIsInterface(false) + .build(); + + ClassName successor = replacementSiteOwnerAndNameRepresentative.owner(); + logger.atInfo().log("--> Representative-based replacementSite: %s", replacementSite); + if (successor.isInDesugarRuntimeLibrary()) { + requiredRuntimeSupportTypes.add(successor); + } + replacementSite.acceptTypeMapper(ClassName.SHADOWED_TO_MIRRORED_TYPE_MAPPER).accept(mv); + return true; + } + return false; + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget.proto b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget.proto new file mode 100644 index 00000000000000..cb46a17c23ab51 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget.proto @@ -0,0 +1,43 @@ +/* + * Copyright 2020 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. + */ + +syntax = "proto3"; + +package android.desugar; + +import "src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/desugar_method_attr.proto"; + +option java_package = "com.google.devtools.build.android.desugar.retarget"; +option java_multiple_files = true; + +message MethodInvocations { + repeated MethodInvocationReplacement replacements = 1; +} + +message MethodInvocationReplacement { + MethodInvocation source = 1; + MethodInvocation destination = 2; + repeated ReplacementRange range = 3; + bool auto_deduce_opcode_and_desc = 4; +} + +enum ReplacementRange { + ALL = 0; + REPLACE_CALLS_TO_LONG_UNSIGNED = 1; + REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS = 2; + DESUGAR_JAVA8_LIBS = 3; + DESUGAR_JAVA8_CORE_LIBS = 4; +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget_config.textproto b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget_config.textproto new file mode 100644 index 00000000000000..d0bad07554e020 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget_config.textproto @@ -0,0 +1,1419 @@ +# proto-file: third_party/bazel/src/tools/android/java/com/google/devtools/build/android/desugar/retarget/retarget.proto +# proto-message: MethodInvocations + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Integer" + name: "hashCode" + desc: "(I)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode" + name: "identityAsHashCode" + desc: "(I)I" + } + is_interface: false + } + range: REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Short" + name: "hashCode" + desc: "(S)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode" + name: "identityAsHashCode" + desc: "(S)I" + } + is_interface: false + } + range: REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Byte" + name: "hashCode" + desc: "(B)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode" + name: "identityAsHashCode" + desc: "(B)I" + } + is_interface: false + } + range: REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Character" + name: "hashCode" + desc: "(C)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode" + name: "identityAsHashCode" + desc: "(C)I" + } + is_interface: false + } + range: REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Float" + name: "hashCode" + desc: "(F)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode" + name: "hashCode" + desc: "(F)I" + } + is_interface: false + } + range: REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Boolean" + name: "hashCode" + desc: "(Z)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode" + name: "hashCode" + desc: "(Z)I" + } + is_interface: false + } + range: REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Long" + name: "hashCode" + desc: "(J)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode" + name: "hashCode" + desc: "(J)I" + } + is_interface: false + } + range: REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Double" + name: "hashCode" + desc: "(D)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode" + name: "hashCode" + desc: "(D)I" + } + is_interface: false + } + range: REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Integer" + name: "divideUnsigned" + desc: "(II)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/UnsignedInts" + name: "divide" + desc: "(II)I" + } + is_interface: false + } + # TODO(deltazulu): adjust flag name to include INT_UNSIGNED + range: REPLACE_CALLS_TO_LONG_UNSIGNED +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Integer" + name: "remainderUnsigned" + desc: "(II)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/UnsignedInts" + name: "remainder" + desc: "(II)I" + } + is_interface: false + } + range: REPLACE_CALLS_TO_LONG_UNSIGNED +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Integer" + name: "toUnsignedString" + desc: "(I)Ljava/lang/String;" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/UnsignedInts" + name: "toString" + desc: "(I)Ljava/lang/String;" + } + is_interface: false + } + range: REPLACE_CALLS_TO_LONG_UNSIGNED +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Integer" + name: "toUnsignedString" + desc: "(II)Ljava/lang/String;" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/UnsignedInts" + name: "toString" + desc: "(II)Ljava/lang/String;" + } + is_interface: false + } + range: REPLACE_CALLS_TO_LONG_UNSIGNED +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Long" + name: "divideUnsigned" + desc: "(JJ)J" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/UnsignedLongs" + name: "divideUnsigned" + desc: "(JJ)J" + } + is_interface: false + } + range: REPLACE_CALLS_TO_LONG_UNSIGNED +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Long" + name: "remainderUnsigned" + desc: "(JJ)J" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/UnsignedLongs" + name: "remainderUnsigned" + desc: "(JJ)J" + } + is_interface: false + } + range: REPLACE_CALLS_TO_LONG_UNSIGNED +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Long" + name: "toUnsignedString" + desc: "(J)Ljava/lang/String;" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/UnsignedLongs" + name: "toString" + desc: "(J)Ljava/lang/String;" + } + is_interface: false + } + range: REPLACE_CALLS_TO_LONG_UNSIGNED +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Long" + name: "toUnsignedString" + desc: "(JI)Ljava/lang/String;" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "com/google/devtools/build/android/desugar/runtime/UnsignedLongs" + name: "toString" + desc: "(JI)Ljava/lang/String;" + } + is_interface: false + } + range: REPLACE_CALLS_TO_LONG_UNSIGNED +} + +# ==== DESUGAR_JAVA8_LIBS ==== + +replacements { + source { + method_id { + owner: "java/lang/Double" + name: "max" + } + } + destination { + method_id { + owner: "java/lang/DesugarDouble" + name: "max" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Double" + name: "min" + } + } + destination { + method_id { + owner: "java/lang/DesugarDouble" + name: "min" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Double" + name: "sum" + } + } + destination { + method_id { + owner: "java/lang/DesugarDouble" + name: "sum" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Double" + name: "hashCode" + desc: "(D)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "java/lang/DesugarDouble" + name: "hashCode" + desc: "(D)I" + } + is_interface: false + } + range: DESUGAR_JAVA8_CORE_LIBS +} + +replacements { + source { + opcode: 182 + method_id { + owner: "java/lang/Double" + name: "hashCode" + desc: "()I" + } + } + destination { + opcode: 184 + method_id { + owner: "java/lang/DesugarDouble" + name: "hashCode" + desc: "(D)I" + } + } + range: DESUGAR_JAVA8_CORE_LIBS +} + +replacements { + source { + method_id { + owner: "java/lang/Integer" + name: "max" + } + } + destination { + method_id { + owner: "java/lang/DesugarInteger" + name: "max" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Integer" + name: "min" + } + } + destination { + method_id { + owner: "java/lang/DesugarInteger" + name: "min" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Integer" + name: "sum" + } + } + destination { + method_id { + owner: "java/lang/DesugarInteger" + name: "sum" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Integer" + name: "toUnsignedLong" + } + } + destination { + method_id { + owner: "java/lang/DesugarInteger" + name: "toUnsignedLong" + } + } + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Integer" + name: "hashCode" + desc: "(I)I" + } + } + destination { + opcode: 184 + method_id { + owner: "java/lang/DesugarInteger" + name: "hashCode" + desc: "(I)I" + } + } + range: DESUGAR_JAVA8_CORE_LIBS +} + +replacements { + source { + opcode: 182 + method_id { + owner: "java/lang/Integer" + name: "hashCode" + desc: "()I" + } + } + destination { + opcode: 184 + method_id { + owner: "java/lang/DesugarInteger" + name: "hashCode" + desc: "(I)I" + } + } + range: DESUGAR_JAVA8_CORE_LIBS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Integer" + name: "parseInt" + desc: "(Ljava/lang/CharSequence;III)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "java/lang/DesugarInteger" + name: "parseInt" + desc: "(Ljava/lang/CharSequence;III)I" + } + is_interface: false + } + range: DESUGAR_JAVA8_CORE_LIBS +} + +replacements { + source { + method_id { + owner: "java/lang/Long" + name: "max" + } + } + destination { + method_id { + owner: "java/lang/DesugarLong" + name: "max" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Long" + name: "min" + } + } + destination { + method_id { + owner: "java/lang/DesugarLong" + name: "min" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Long" + name: "sum" + } + } + destination { + method_id { + owner: "java/lang/DesugarLong" + name: "sum" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Long" + name: "hashCode" + desc: "(J)I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "java/lang/DesugarLong" + name: "hashCode" + desc: "(J)I" + } + } + range: DESUGAR_JAVA8_CORE_LIBS +} + +replacements { + source { + opcode: 182 + method_id { + owner: "java/lang/Long" + name: "hashCode" + desc: "()I" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "java/lang/DesugarLong" + name: "hashCode" + desc: "(J)I" + } + } + range: DESUGAR_JAVA8_CORE_LIBS +} + +replacements { + source { + method_id { + owner: "java/lang/Long" + name: "divideUnsigned" + } + } + destination { + method_id { + owner: "java/lang/DesugarLong" + name: "divideUnsigned" + } + } + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/lang/Long" + name: "parseLong" + desc: "(Ljava/lang/CharSequence;III)J" + } + is_interface: false + } + destination { + opcode: 184 + method_id { + owner: "java/lang/DesugarLong" + name: "parseLong" + desc: "(Ljava/lang/CharSequence;III)J" + } + is_interface: false + } + range: DESUGAR_JAVA8_CORE_LIBS +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "addExact" + } + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "addExact" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "subtractExact" + } + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "subtractExact" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "multiplyExact" + } + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "multiplyExact" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "incrementExact" + } + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "incrementExact" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "decrementExact" + } + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "decrementExact" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "negateExact" + } + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "negateExact" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "toIntExact" + } + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "toIntExact" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "multiplyFull" + } + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "multiplyFull" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "multiplyHigh" + } + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "multiplyHigh" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "floorDiv" + } + is_interface: false + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "floorDiv" + } + is_interface: false + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "floorMod" + } + is_interface: false + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "floorMod" + } + is_interface: false + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Math" + name: "nextDown" + } + is_interface: false + } + destination { + method_id { + owner: "java/lang/DesugarMath" + name: "nextDown" + } + is_interface: false + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/io/BufferedReader" + name: "lines" + } + } + destination { + method_id { + owner: "java/io/DesugarBufferedReader" + name: "lines" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/Arrays" + name: "stream" + } + } + destination { + method_id { + owner: "java/util/DesugarArrays" + name: "stream" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/Arrays" + name: "spliterator" + } + } + destination { + method_id { + owner: "java/util/DesugarArrays" + name: "spliterator" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/Calendar" + name: "toInstant" + } + } + destination { + method_id { + owner: "java/util/DesugarCalendar" + name: "toInstant" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/Date" + name: "from" + } + } + destination { + method_id { + owner: "java/util/DesugarDate" + name: "from" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/Date" + name: "toInstant" + } + } + destination { + method_id { + owner: "java/util/DesugarDate" + name: "toInstant" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/GregorianCalendar" + name: "from" + } + } + destination { + method_id { + owner: "java/util/DesugarGregorianCalendar" + name: "from" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/GregorianCalendar" + name: "toZonedDateTime" + } + } + destination { + method_id { + owner: "java/util/DesugarGregorianCalendar" + name: "toZonedDateTime" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + opcode: 182 + method_id { + owner: "java/util/LinkedHashSet" + name: "spliterator" + desc: "()Ljava/util/Spliterator;" + } + } + destination { + opcode: 184 + method_id { + owner: "java/util/DesugarLinkedHashSet" + name: "spliterator" + desc: "(Ljava/util/LinkedHashSet;)Ljava/util/Spliterator;" + } + } + range: DESUGAR_JAVA8_CORE_LIBS + range: DESUGAR_JAVA8_LIBS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/util/TimeZone" + name: "getTimeZone" + desc: "(Ljava/time/ZoneId;)Ljava/util/TimeZone;" + } + } + destination { + opcode: 184 + method_id { + owner: "java/util/DesugarTimeZone" + name: "getTimeZone" + desc: "(Ljava/time/ZoneId;)Ljava/util/TimeZone;" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS +} + +replacements { + source { + opcode: 184 + method_id { + owner: "java/util/TimeZone" + name: "getTimeZone" + desc: "(Ljava/lang/String;)Ljava/util/TimeZone;" + } + } + destination { + opcode: 184 + method_id { + owner: "java/util/DesugarTimeZone" + name: "getTimeZone" + desc: "(Ljava/lang/String;)Ljava/util/TimeZone;" + } + } + range: DESUGAR_JAVA8_LIBS +} + +replacements { + source { + opcode: 182 + method_id { + owner: "java/util/TimeZone" + name: "toZoneId" + desc: "()Ljava/time/ZoneId;" + } + } + destination { + opcode: 184 + method_id { + owner: "java/util/DesugarTimeZone" + name: "toZoneId" + desc: "(Ljava/util/TimeZone;)Ljava/time/ZoneId;" + } + } + range: DESUGAR_JAVA8_CORE_LIBS + range: DESUGAR_JAVA8_LIBS +} + +replacements { + source { + method_id { + owner: "java/util/Collections" + name: "synchronizedMap" + } + } + destination { + method_id { + owner: "java/util/DesugarCollections" + name: "synchronizedMap" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/Collections" + name: "synchronizedSortedMap" + } + } + destination { + method_id { + owner: "java/util/DesugarCollections" + name: "synchronizedSortedMap" + } + } + range: DESUGAR_JAVA8_LIBS + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/lang/Iterable" + name: "forEach" + } + } + destination { + method_id { + owner: "java/util/DesugarCollections" + name: "forEachIterable" + } + } + range: DESUGAR_JAVA8_CORE_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicInteger" + name: "getAndUpdate" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicInteger" + name: "getAndUpdate" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicInteger" + name: "updateAndGet" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicInteger" + name: "updateAndGet" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicInteger" + name: "getAndAccumulate" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicInteger" + name: "getAndAccumulate" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicInteger" + name: "accumulateAndGet" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicInteger" + name: "accumulateAndGet" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicLong" + name: "getAndUpdate" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicLong" + name: "getAndUpdate" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicLong" + name: "updateAndGet" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicLong" + name: "updateAndGet" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicLong" + name: "getAndAccumulate" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicLong" + name: "getAndAccumulate" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicLong" + name: "accumulateAndGet" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicLong" + name: "accumulateAndGet" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicReference" + name: "getAndUpdate" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicReference" + name: "getAndUpdate" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicReference" + name: "updateAndGet" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicReference" + name: "updateAndGet" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicReference" + name: "getAndAccumulate" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicReference" + name: "getAndAccumulate" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + +replacements { + source { + method_id { + owner: "java/util/concurrent/atomic/AtomicReference" + name: "accumulateAndGet" + } + } + destination { + method_id { + owner: "java/util/concurrent/atomic/DesugarAtomicReference" + name: "accumulateAndGet" + } + } + range: DESUGAR_JAVA8_LIBS + auto_deduce_opcode_and_desc: true +} + diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/BUILD new file mode 100644 index 00000000000000..5c6bafd0ece14d --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/BUILD @@ -0,0 +1,49 @@ +load("@rules_java//java:defs.bzl", "java_library") + +# Description: +# This is the extension package to support desugaring try-with-resources statements. +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__", + ], +) + +java_library( + name = "throwable_extension", + srcs = ["ThrowableExtension.java"], + # This library must be compiled with java7, as we directly copy it to the desugared jar. + javacopts = [ + "-source 7", + "-target 7", + ], +) + +java_library( + name = "primitives", + srcs = [ + "PrimitiveHashcode.java", + "UnsignedInts.java", + "UnsignedLongs.java", + ], + # This library must be compiled with java7, as we directly copy it to the desugared jar. + javacopts = [ + "-source 7", + "-target 7", + ], +) + +java_library( + name = "string_concats", + srcs = ["StringConcats.java"], + # This library must be compiled with java7, as we directly copy it to the desugared jar. + javacopts = [ + "-source 7", + "-target 7", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode.java b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode.java new file mode 100644 index 00000000000000..50d4de52af1716 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/PrimitiveHashcode.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 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.runtime; + +/** Backward-compatibility implementation for the hash code of primitive types. */ +public class PrimitiveHashcode { + + public static int hashCode(float value) { + return Float.floatToIntBits(value); + } + + public static int hashCode(boolean value) { + return value ? 1231 : 1237; + } + + public static int hashCode(long value) { + return (int) (value ^ value >>> 32); + } + + public static int hashCode(double value) { + long bits = Double.doubleToLongBits(value); + return (int) (bits ^ (bits >>> 32)); + } + + public static int identityAsHashCode(int value) { + return value; + } + + public static int identityAsHashCode(short value) { + return value; + } + + public static int identityAsHashCode(byte value) { + return value; + } + + public static int identityAsHashCode(char value) { + return value; + } + + private PrimitiveHashcode() {} +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/StringConcats.java b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/StringConcats.java new file mode 100644 index 00000000000000..0c29d3bb73ac5f --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/StringConcats.java @@ -0,0 +1,39 @@ +// Copyright 2019 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.runtime; + +/** The runtime library used for string concatenation. */ +public class StringConcats { + + /** + * Concatenate strings in a way compliant with the Javadoc for {@link + * java.lang.invoke.StringConcatFactory#makeConcatWithConstants}. + */ + public static String concat(Object[] runtimeTexts, String recipe, Object[] constants) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0, runTimeTextIndex = 0, constantIndex = 0; i < recipe.length(); i++) { + char c = recipe.charAt(i); + if (c == '\1') { + stringBuilder.append(runtimeTexts[runTimeTextIndex++]); + } else if (c == '\2') { + stringBuilder.append(constants[constantIndex++]); + } else { + stringBuilder.append(c); + } + } + return stringBuilder.toString(); + } + + private StringConcats() {} +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtension.java b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtension.java new file mode 100644 index 00000000000000..c19320d35149e3 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtension.java @@ -0,0 +1,412 @@ +// 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.runtime; + +import java.io.Closeable; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This is an extension class for java.lang.Throwable. It emulates the methods + * addSuppressed(Throwable) and getSuppressed(), so the language feature try-with-resources can be + * used on Android devices whose API level is below 19. + * + *

    Note that the Desugar should avoid desugaring this class. + */ +public final class ThrowableExtension { + + static final AbstractDesugaringStrategy STRATEGY; + /** + * This property allows users to change the desugared behavior of try-with-resources at runtime. + * If its value is {@code true}, then {@link MimicDesugaringStrategy} will NOT be used, and {@link + * NullDesugaringStrategy} is used instead. + * + *

    Note: this property is ONLY used when the API level on the device is below 19. + */ + public static final String SYSTEM_PROPERTY_TWR_DISABLE_MIMIC = + "com.google.devtools.build.android.desugar.runtime.twr_disable_mimic"; + + // Visible for testing. + static final int API_LEVEL; + + static { + AbstractDesugaringStrategy strategy; + Integer apiLevel = null; + try { + apiLevel = readApiLevelFromBuildVersion(); + if (apiLevel != null && apiLevel.intValue() >= 19) { + strategy = new ReuseDesugaringStrategy(); + } else if (useMimicStrategy()) { + strategy = new MimicDesugaringStrategy(); + } else { + strategy = new NullDesugaringStrategy(); + } + } catch (Throwable e) { + // This catchall block is intentionally created to avoid anything unexpected, so that + // the desugared app will continue running in case of exceptions. + System.err.println( + "An error has occurred when initializing the try-with-resources desuguring strategy. " + + "The default strategy " + + NullDesugaringStrategy.class.getName() + + "will be used. The error is: "); + e.printStackTrace(System.err); + strategy = new NullDesugaringStrategy(); + } + STRATEGY = strategy; + API_LEVEL = apiLevel == null ? 1 : apiLevel.intValue(); + } + + public static AbstractDesugaringStrategy getStrategy() { + return STRATEGY; + } + + public static void addSuppressed(Throwable receiver, Throwable suppressed) { + STRATEGY.addSuppressed(receiver, suppressed); + } + + public static Throwable[] getSuppressed(Throwable receiver) { + return STRATEGY.getSuppressed(receiver); + } + + public static void printStackTrace(Throwable receiver) { + STRATEGY.printStackTrace(receiver); + } + + public static void printStackTrace(Throwable receiver, PrintWriter writer) { + STRATEGY.printStackTrace(receiver, writer); + } + + public static void printStackTrace(Throwable receiver, PrintStream stream) { + STRATEGY.printStackTrace(receiver, stream); + } + + public static void closeResource(Throwable throwable, Object resource) throws Throwable { + if (resource == null) { + return; + } + try { + if (API_LEVEL >= 19) { + ((AutoCloseable) resource).close(); + } else { + if (resource instanceof Closeable) { + ((Closeable) resource).close(); + } else { + try { + Method method = resource.getClass().getMethod("close"); + method.invoke(resource); + } catch (NoSuchMethodException | SecurityException e) { + throw new AssertionError(resource.getClass() + " does not have a close() method.", e); + } catch (IllegalAccessException + | IllegalArgumentException + | ExceptionInInitializerError e) { + throw new AssertionError("Fail to call close() on " + resource.getClass(), e); + } catch (InvocationTargetException e) { + // Exception occurs during the invocation to the close method. The cause is the real + // exception. + Throwable cause = e.getCause(); + throw cause; + } + } + } + } catch (Throwable e) { + if (throwable != null) { + addSuppressed(throwable, e); + throw throwable; + } else { + throw e; + } + } + } + + private static boolean useMimicStrategy() { + return !Boolean.getBoolean(SYSTEM_PROPERTY_TWR_DISABLE_MIMIC); + } + + private static final String ANDROID_OS_BUILD_VERSION = "android.os.Build$VERSION"; + + /** + * Get the API level from {@link android.os.Build.VERSION} via reflection. The reason to use + * relection is to avoid dependency on {@link android.os.Build.VERSION}. The advantage of doing + * this is that even when you desugar a jar twice, and Desugars sees this class, there is no need + * to put {@link android.os.Build.VERSION} on the classpath. + * + *

    Another reason of doing this is that it does not introduce any additional dependency into + * the input jars. + * + * @return The API level of the current device. If it is {@code null}, then it means there was an + * exception. + */ + private static Integer readApiLevelFromBuildVersion() { + try { + Class buildVersionClass = Class.forName(ANDROID_OS_BUILD_VERSION); + Field field = buildVersionClass.getField("SDK_INT"); + return (Integer) field.get(null); + } catch (Exception e) { + System.err.println( + "Failed to retrieve value from " + + ANDROID_OS_BUILD_VERSION + + ".SDK_INT due to the following exception."); + e.printStackTrace(System.err); + return null; + } + } + + /** + * The strategy to desugar try-with-resources statements. A strategy handles the behavior of an + * exception in terms of suppressed exceptions and stack trace printing. + */ + public abstract static class AbstractDesugaringStrategy { + + protected static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0]; + + public abstract void addSuppressed(Throwable receiver, Throwable suppressed); + + public abstract Throwable[] getSuppressed(Throwable receiver); + + public abstract void printStackTrace(Throwable receiver); + + public abstract void printStackTrace(Throwable receiver, PrintStream stream); + + public abstract void printStackTrace(Throwable receiver, PrintWriter writer); + } + + /** This strategy just delegates all the method calls to java.lang.Throwable. */ + public static final class ReuseDesugaringStrategy extends AbstractDesugaringStrategy { + + @Override + public void addSuppressed(Throwable receiver, Throwable suppressed) { + receiver.addSuppressed(suppressed); + } + + @Override + public Throwable[] getSuppressed(Throwable receiver) { + return receiver.getSuppressed(); + } + + @Override + public void printStackTrace(Throwable receiver) { + receiver.printStackTrace(); + } + + @Override + public void printStackTrace(Throwable receiver, PrintStream stream) { + receiver.printStackTrace(stream); + } + + @Override + public void printStackTrace(Throwable receiver, PrintWriter writer) { + receiver.printStackTrace(writer); + } + } + + /** This strategy mimics the behavior of suppressed exceptions with a map. */ + public static final class MimicDesugaringStrategy extends AbstractDesugaringStrategy { + + static final String SUPPRESSED_PREFIX = "Suppressed: "; + private final ConcurrentWeakIdentityHashMap map = new ConcurrentWeakIdentityHashMap(); + + /** + * Suppress an exception. If the exception to be suppressed is {@code receiver} or {@null}, an + * exception will be thrown. + */ + @Override + public void addSuppressed(Throwable receiver, Throwable suppressed) { + if (suppressed == receiver) { + throw new IllegalArgumentException("Self suppression is not allowed.", suppressed); + } + if (suppressed == null) { + throw new NullPointerException("The suppressed exception cannot be null."); + } + // The returned list is a synchrnozed list. + map.get(receiver, /*createOnAbsence=*/ true).add(suppressed); + } + + @Override + public Throwable[] getSuppressed(Throwable receiver) { + List list = map.get(receiver, /*createOnAbsence=*/ false); + if (list == null || list.isEmpty()) { + return EMPTY_THROWABLE_ARRAY; + } + return list.toArray(EMPTY_THROWABLE_ARRAY); + } + + /** + * Print the stack trace for the parameter {@code receiver}. Note that it is deliberate to NOT + * reuse the implementation {@code MimicDesugaringStrategy.printStackTrace(Throwable, + * PrintStream)}, because we are not sure whether the developer prints the stack trace to a + * different stream other than System.err. Therefore, it is a caveat that the stack traces of + * {@code receiver} and its suppressed exceptions are printed in two different streams. + */ + @Override + public void printStackTrace(Throwable receiver) { + receiver.printStackTrace(); + List suppressedList = map.get(receiver, /*createOnAbsence=*/ false); + if (suppressedList == null) { + return; + } + synchronized (suppressedList) { + for (Throwable suppressed : suppressedList) { + System.err.print(SUPPRESSED_PREFIX); + suppressed.printStackTrace(); + } + } + } + + @Override + public void printStackTrace(Throwable receiver, PrintStream stream) { + receiver.printStackTrace(stream); + List suppressedList = map.get(receiver, /*createOnAbsence=*/ false); + if (suppressedList == null) { + return; + } + synchronized (suppressedList) { + for (Throwable suppressed : suppressedList) { + stream.print(SUPPRESSED_PREFIX); + suppressed.printStackTrace(stream); + } + } + } + + @Override + public void printStackTrace(Throwable receiver, PrintWriter writer) { + receiver.printStackTrace(writer); + List suppressedList = map.get(receiver, /*createOnAbsence=*/ false); + if (suppressedList == null) { + return; + } + synchronized (suppressedList) { + for (Throwable suppressed : suppressedList) { + writer.print(SUPPRESSED_PREFIX); + suppressed.printStackTrace(writer); + } + } + } + } + + /** A hash map, that is concurrent, weak-key, and identity-hashing. */ + public static final class ConcurrentWeakIdentityHashMap { + + private final ConcurrentHashMap> map = + new ConcurrentHashMap<>(16, 0.75f, 10); + private final ReferenceQueue referenceQueue = new ReferenceQueue<>(); + + /** + * @param throwable, the key to retrieve or create associated list. + * @param createOnAbsence {@code true} to create a new list if there is no value for the key. + * @return the associated value with the given {@code throwable}. If {@code createOnAbsence} is + * {@code true}, the returned value will be non-null. Otherwise, it can be {@code null} + */ + public List get(Throwable throwable, boolean createOnAbsence) { + deleteEmptyKeys(); + WeakKey keyForQuery = new WeakKey(throwable, null); + List list = map.get(keyForQuery); + if (!createOnAbsence) { + return list; + } + if (list != null) { + return list; + } + List newValue = new Vector<>(2); + list = map.putIfAbsent(new WeakKey(throwable, referenceQueue), newValue); + return list == null ? newValue : list; + } + + /** For testing-purpose */ + int size() { + return map.size(); + } + + void deleteEmptyKeys() { + // The ReferenceQueue.poll() is thread-safe. + for (Reference key = referenceQueue.poll(); key != null; key = referenceQueue.poll()) { + map.remove(key); + } + } + + private static final class WeakKey extends WeakReference { + + /** + * The hash code is used later to retrieve the entry, of which the key is the current weak + * key. If the referent is marked for garbage collection and is set to null, we are still able + * to locate the entry. + */ + private final int hash; + + public WeakKey(Throwable referent, ReferenceQueue q) { + super(referent, q); + if (referent == null) { + throw new NullPointerException("The referent cannot be null"); + } + hash = System.identityHashCode(referent); + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) { + return false; + } + if (this == obj) { + return true; + } + WeakKey other = (WeakKey) obj; + // Note that, after the referent is garbage collected, then the referent will be null. + // And the equality test still holds. + return this.hash == other.hash && this.get() == other.get(); + } + } + } + + /** This strategy ignores all suppressed exceptions, which is how retrolambda does. */ + static final class NullDesugaringStrategy extends AbstractDesugaringStrategy { + + @Override + public void addSuppressed(Throwable receiver, Throwable suppressed) { + // Do nothing. The suppressed exception is discarded. + } + + @Override + public Throwable[] getSuppressed(Throwable receiver) { + return EMPTY_THROWABLE_ARRAY; + } + + @Override + public void printStackTrace(Throwable receiver) { + receiver.printStackTrace(); + } + + @Override + public void printStackTrace(Throwable receiver, PrintStream stream) { + receiver.printStackTrace(stream); + } + + @Override + public void printStackTrace(Throwable receiver, PrintWriter writer) { + receiver.printStackTrace(writer); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedInts.java b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedInts.java new file mode 100644 index 00000000000000..05cc10134cea70 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedInts.java @@ -0,0 +1,102 @@ +// Copyright 2020 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.runtime; + +/** + * Static utility methods pertaining to {@code int} primitives that interpret values as + * unsigned (that is, any negative value {@code x} is treated as the positive value {@code + * 2^32 + x}). The methods for which signedness is not an issue are in {@link Ints}, as well as + * signed versions of methods for which signedness is an issue. + * + *

    See the Guava User Guide article on unsigned + * primitive utilities. + */ +public final class UnsignedInts { + static final long INT_MASK = 0xffffffffL; + + private UnsignedInts() {} + + private static int flip(int value) { + return value ^ Integer.MIN_VALUE; + } + + /** + * Compares the two specified {@code int} values, treating them as unsigned values between {@code + * 0} and {@code 2^32 - 1} inclusive. + * + * @param a the first unsigned {@code int} to compare + * @param b the second unsigned {@code int} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + /*visible for testing*/ static int compare(int a, int b) { + a = flip(a); + b = flip(b); + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Returns the value of the given {@code int} as a {@code long}, when treated as unsigned. + */ + /*visible for testing*/ static long toLong(int value) { + return value & INT_MASK; + } + + /** + * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 32-bit + * quantities. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static int divide(int dividend, int divisor) { + return (int) (toLong(dividend) / toLong(divisor)); + } + + /** + * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 32-bit + * quantities. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static int remainder(int dividend, int divisor) { + return (int) (toLong(dividend) % toLong(divisor)); + } + + /** + * Returns a string representation of x, where x is treated as unsigned. + */ + public static String toString(int x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as + * unsigned. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + */ + public static String toString(int x, int radix) { + long asLong = x & INT_MASK; + return Long.toString(asLong, radix); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongs.java b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongs.java new file mode 100644 index 00000000000000..e5dde349d919d5 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongs.java @@ -0,0 +1,178 @@ +// Copyright 2019 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.runtime; + +/** + * Static utility methods pertaining to {@code long} primitives that interpret values as + * unsigned (that is, any negative value {@code x} is treated as the positive value {@code + * 2^64 + x}), based on Guava's implementation. + * + *

    See the Guava User Guide article on unsigned + * primitive utilities. + */ +public final class UnsignedLongs { + private UnsignedLongs() {} + + /** + * A (self-inverse) bijection which converts the ordering on unsigned longs to the ordering on + * longs, that is, {@code a <= b} as unsigned longs if and only if {@code flip(a) <= flip(b)} as + * signed longs. + */ + private static long flip(long a) { + return a ^ Long.MIN_VALUE; + } + + /** + * Compares the two specified {@code long} values, treating them as unsigned values between {@code + * 0} and {@code 2^64 - 1} inclusive. + * + * @param a the first unsigned {@code long} to compare + * @param b the second unsigned {@code long} to compare + * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is + * greater than {@code b}; or zero if they are equal + */ + /*visible for testing*/ static int compare(long a, long b) { + a = flip(a); + b = flip(b); + // TODO(kmb): Could use Long.compare here if we desugared with LongCompareMethodRewriter + return (a < b) ? -1 : ((a > b) ? 1 : 0); + } + + /** + * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 64-bit + * quantities. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static long divideUnsigned(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return 0; // dividend < divisor + } else { + return 1; // dividend >= divisor + } + } + + // Optimization - use signed division if dividend < 2^63 + if (dividend >= 0) { + return dividend / divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from fact that + * floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not quite + * trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return quotient + (compare(rem, divisor) >= 0 ? 1 : 0); + } + + /** + * Returns dividend % divisor, where the dividend and divisor are treated as unsigned 64-bit + * quantities. + * + * @param dividend the dividend (numerator) + * @param divisor the divisor (denominator) + * @throws ArithmeticException if divisor is 0 + */ + public static long remainderUnsigned(long dividend, long divisor) { + if (divisor < 0) { // i.e., divisor >= 2^63: + if (compare(dividend, divisor) < 0) { + return dividend; // dividend < divisor + } else { + return dividend - divisor; // dividend >= divisor + } + } + + // Optimization - use signed modulus if dividend < 2^63 + if (dividend >= 0) { + return dividend % divisor; + } + + /* + * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is + * guaranteed to be either exact or one less than the correct value. This follows from the fact + * that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not + * quite trivial. + */ + long quotient = ((dividend >>> 1) / divisor) << 1; + long rem = dividend - quotient * divisor; + return rem - (compare(rem, divisor) >= 0 ? divisor : 0); + } + + /** Returns a string representation of x, where x is treated as unsigned. */ + public static String toString(long x) { + return toString(x, 10); + } + + /** + * Returns a string representation of {@code x} for the given radix, where {@code x} is treated as + * unsigned. + * + * @param x the value to convert to a string. + * @param radix the radix to use while working with {@code x} + * @throws IllegalArgumentException if {@code radix} is not between {@link Character#MIN_RADIX} + * and {@link Character#MAX_RADIX}. + */ + public static String toString(long x, int radix) { + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + throw new IllegalArgumentException( + "radix (" + radix + ") must be between Character.MIN_RADIX and Character.MAX_RADIX"); + } + if (x == 0) { + // Simply return "0" + return "0"; + } else if (x > 0) { + return Long.toString(x, radix); + } else { + char[] buf = new char[64]; + int i = buf.length; + if ((radix & (radix - 1)) == 0) { + // Radix is a power of two so we can avoid division. + int shift = Integer.numberOfTrailingZeros(radix); + int mask = radix - 1; + do { + buf[--i] = Character.forDigit(((int) x) & mask, radix); + x >>>= shift; + } while (x != 0); + } else { + // Separate off the last digit using unsigned division. That will leave + // a number that is nonnegative as a signed integer. + long quotient; + if ((radix & 1) == 0) { + // Fast path for the usual case where the radix is even. + quotient = (x >>> 1) / (radix >>> 1); + } else { + quotient = divideUnsigned(x, radix); + } + long rem = x - quotient * radix; + buf[--i] = Character.forDigit((int) rem, radix); + x = quotient; + // Simple modulo/division approach + while (x > 0) { + buf[--i] = Character.forDigit((int) (x % radix), radix); + x /= radix; + } + } + // Generate string + return new String(buf, i, buf.length - i); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/BUILD new file mode 100644 index 00000000000000..e42aaeb994c63d --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/BUILD @@ -0,0 +1,28 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +java_library( + name = "strconcat", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java new file mode 100644 index 00000000000000..65cd40772b03d9 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/strconcat/IndyStringConcatDesugaring.java @@ -0,0 +1,171 @@ +/* + * Copyright 2019 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.strconcat; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.devtools.build.android.desugar.langmodel.LangModelHelper.visitPushInstr; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberUseCounter; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.LangModelHelper; +import com.google.devtools.build.android.desugar.langmodel.MemberUseKind; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import org.objectweb.asm.Opcodes; +import java.util.Arrays; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** Desugars indy string concatenations by replacement with string builders. */ +public final class IndyStringConcatDesugaring extends ClassVisitor { + + public static final MethodInvocationSite INVOKE_JDK11_STRING_CONCAT = + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKEDYNAMIC) + .setMethod( + MethodKey.create( + ClassName.create("java/lang/invoke/StringConcatFactory"), + "makeConcatWithConstants", + "(Ljava/lang/invoke/MethodHandles$Lookup;" + + "Ljava/lang/String;" + + "Ljava/lang/invoke/MethodType;" + + "Ljava/lang/String;" + + "[Ljava/lang/Object;)" + + "Ljava/lang/invoke/CallSite;")) + .setIsInterface(false) + .build(); + + private static final MethodInvocationSite INVOKE_STRING_CONCAT_REPLACEMENT_METHOD = + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKESTATIC) + .setMethod( + MethodKey.create( + ClassName.create( + "com/google/devtools/build/android/desugar/runtime/StringConcats"), + "concat", + "([Ljava/lang/Object;" + + "Ljava/lang/String;" + + "[Ljava/lang/Object;)" + + "Ljava/lang/String;")) + .setIsInterface(false) + .build(); + + private static final MethodInvocationSite INVOKE_STRING_CONCAT_REPLACEMENT_CORE_LIB_METHOD = + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKESTATIC) + .setMethod( + MethodKey.create( + ClassName.create("java/lang/DesugarStringConcats"), + "concat", + "([Ljava/lang/Object;" + + "Ljava/lang/String;" + + "[Ljava/lang/Object;)" + + "Ljava/lang/String;")) + .setIsInterface(false) + .build(); + + private final ClassMemberUseCounter classMemberUseCounter; + private final MethodInvocationSite invokeStringConcatReplacementSite; + + public IndyStringConcatDesugaring( + ClassMemberUseCounter classMemberUseCounter, ClassVisitor classVisitor, boolean coreLibrary) { + super(Opcodes.ASM9, classVisitor); + this.classMemberUseCounter = classMemberUseCounter; + this.invokeStringConcatReplacementSite = + coreLibrary + ? INVOKE_STRING_CONCAT_REPLACEMENT_CORE_LIB_METHOD + : INVOKE_STRING_CONCAT_REPLACEMENT_METHOD; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return mv == null + ? null + : new IndifiedStringConcatInvocationConverter( + classMemberUseCounter, api, mv, invokeStringConcatReplacementSite); + } + + static final class IndifiedStringConcatInvocationConverter extends MethodVisitor { + + private final ClassMemberUseCounter classMemberUseCounter; + private final MethodInvocationSite invokeStringConcatReplacementSite; + + IndifiedStringConcatInvocationConverter( + ClassMemberUseCounter classMemberUseCounter, + int api, + MethodVisitor methodVisitor, + MethodInvocationSite invokeStringConcatReplacementSite) { + super(api, methodVisitor); + this.classMemberUseCounter = classMemberUseCounter; + this.invokeStringConcatReplacementSite = invokeStringConcatReplacementSite; + } + + @Override + public void visitInvokeDynamicInsn( + String name, + String descriptor, + Handle bootstrapMethodHandle, + Object... bootstrapMethodArguments) { + MethodInvocationSite bootstrapMethodInvocation = + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.INVOKEDYNAMIC) + .setMethod( + MethodKey.create( + ClassName.create(bootstrapMethodHandle.getOwner()), + bootstrapMethodHandle.getName(), + bootstrapMethodHandle.getDesc())) + .setIsInterface(false) + .build(); + if (INVOKE_JDK11_STRING_CONCAT.equals(bootstrapMethodInvocation)) { + // Increment the counter for the bootstrap method invocation of + // StringConcatFactory#makeConcatWithConstants + classMemberUseCounter.incrementMemberUseCount(bootstrapMethodInvocation); + + LangModelHelper.collapseStackValuesToObjectArray( + this, + ImmutableList.of(LangModelHelper::anyPrimitiveToStringInvocationSite), + Arrays.stream(Type.getArgumentTypes(descriptor)) + .map(ClassName::create) + .collect(toImmutableList()), + ImmutableList.of()); + + String recipe = (String) bootstrapMethodArguments[0]; + visitLdcInsn(recipe); + + // Stores the constants into an array. + visitPushInstr(mv, bootstrapMethodArguments.length - 1); + visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); + for (int i = 1; i < bootstrapMethodArguments.length; i++) { + visitPushInstr(mv, i - 1); + visitLdcInsn(bootstrapMethodArguments[i]); + visitInsn(Opcodes.AASTORE); + } + + invokeStringConcatReplacementSite.accept(mv); + return; + } + super.visitInvokeDynamicInsn( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ClockConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ClockConverter.java new file mode 100644 index 00000000000000..7fdd80efec378a --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ClockConverter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 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.typeadapter.java.time; + +/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.Clock}. */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class ClockConverter { + + private ClockConverter() {} + + public static j$.time.Clock from(final java.time.Clock clock) { + return clock == null + ? null + : new j$.time.Clock() { + @Override + public j$.time.ZoneId getZone() { + return ZoneIdConverter.from(clock.getZone()); + } + + @Override + public j$.time.Clock withZone(j$.time.ZoneId zone) { + return from(clock.withZone(ZoneIdConverter.to(zone))); + } + + @Override + public j$.time.Instant instant() { + return InstantConverter.from(clock.instant()); + } + }; + } + + public static java.time.Clock to(final j$.time.Clock clock) { + return clock == null + ? null + : new java.time.Clock() { + @Override + public java.time.ZoneId getZone() { + return ZoneIdConverter.to(clock.getZone()); + } + + @Override + public java.time.Clock withZone(java.time.ZoneId zone) { + return to(clock.withZone(ZoneIdConverter.from(zone))); + } + + @Override + public java.time.Instant instant() { + return InstantConverter.to(clock.instant()); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/DurationConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/DurationConverter.java new file mode 100644 index 00000000000000..a20a210f19401a --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/DurationConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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.typeadapter.java.time; + +/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.Duration}. */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class DurationConverter { + + private DurationConverter() {} + + public static j$.time.Duration from(java.time.Duration duration) { + return duration == null + ? null + : j$.time.Duration.ofSeconds(duration.getSeconds(), duration.getNano()); + } + + public static java.time.Duration to(j$.time.Duration duration) { + return duration == null + ? null + : java.time.Duration.ofSeconds(duration.getSeconds(), duration.getNano()); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/InstantConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/InstantConverter.java new file mode 100644 index 00000000000000..7cc37e757cb329 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/InstantConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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.typeadapter.java.time; + +/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.Instant}. */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class InstantConverter { + + private InstantConverter() {} + + public static j$.time.Instant from(java.time.Instant instant) { + return instant == null + ? null + : j$.time.Instant.ofEpochSecond(instant.getEpochSecond(), instant.getNano()); + } + + public static java.time.Instant to(j$.time.Instant instant) { + return instant == null + ? null + : java.time.Instant.ofEpochSecond(instant.getEpochSecond(), instant.getNano()); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalDateConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalDateConverter.java new file mode 100644 index 00000000000000..1794a82d1c8aea --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalDateConverter.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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.typeadapter.java.time; + +/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.LocalDate}. */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class LocalDateConverter { + + private LocalDateConverter() {} + + public static j$.time.LocalDate from(java.time.LocalDate localDate) { + return localDate == null + ? null + : j$.time.LocalDate.of( + localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()); + } + + public static java.time.LocalDate to(j$.time.LocalDate localDate) { + return localDate == null + ? null + : java.time.LocalDate.of( + localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalTimeConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalTimeConverter.java new file mode 100644 index 00000000000000..93dc037582d26b --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/LocalTimeConverter.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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.typeadapter.java.time; + +/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.LocalTime}. */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class LocalTimeConverter { + + private LocalTimeConverter() {} + + public static j$.time.LocalTime from(java.time.LocalTime value) { + return value == null ? null : j$.time.LocalTime.ofNanoOfDay(value.toNanoOfDay()); + } + + public static java.time.LocalTime to(j$.time.LocalTime value) { + return value == null ? null : java.time.LocalTime.ofNanoOfDay(value.toNanoOfDay()); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/MonthDayConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/MonthDayConverter.java new file mode 100644 index 00000000000000..6ab7fbe7475f4f --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/MonthDayConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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.typeadapter.java.time; + +/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.MonthDay}. */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class MonthDayConverter { + + private MonthDayConverter() {} + + public static j$.time.MonthDay from(java.time.MonthDay monthDay) { + return monthDay == null + ? null + : j$.time.MonthDay.of(monthDay.getMonthValue(), monthDay.getDayOfMonth()); + } + + public static java.time.MonthDay to(j$.time.MonthDay monthDay) { + return monthDay == null + ? null + : java.time.MonthDay.of(monthDay.getMonthValue(), monthDay.getDayOfMonth()); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/PeriodConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/PeriodConverter.java new file mode 100644 index 00000000000000..bc14919dcbe5c9 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/PeriodConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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.typeadapter.java.time; + +/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.Period}. */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class PeriodConverter { + + private PeriodConverter() {} + + public static j$.time.Period from(java.time.Period period) { + return period == null + ? null + : j$.time.Period.of(period.getYears(), period.getMonths(), period.getDays()); + } + + public static java.time.Period to(j$.time.Period period) { + return period == null + ? null + : java.time.Period.of(period.getYears(), period.getMonths(), period.getDays()); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ZoneIdConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ZoneIdConverter.java new file mode 100644 index 00000000000000..2716337a175202 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ZoneIdConverter.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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.typeadapter.java.time; + +/** Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.ZoneId}. */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class ZoneIdConverter { + + private ZoneIdConverter() {} + + public static j$.time.ZoneId from(java.time.ZoneId zoneId) { + return zoneId == null ? null : j$.time.ZoneId.of(zoneId.getId()); + } + + public static java.time.ZoneId to(j$.time.ZoneId zoneId) { + return zoneId == null ? null : java.time.ZoneId.of(zoneId.getId()); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ZonedDateTimeConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ZonedDateTimeConverter.java new file mode 100644 index 00000000000000..8199b7b00f126d --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/time/ZonedDateTimeConverter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 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.typeadapter.java.time; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link java.time.ZonedDateTime}. + */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class ZonedDateTimeConverter { + + private ZonedDateTimeConverter() {} + + public static j$.time.ZonedDateTime from(java.time.ZonedDateTime zonedDateTime) { + return zonedDateTime == null + ? null + : j$.time.ZonedDateTime.of( + zonedDateTime.getYear(), + zonedDateTime.getMonthValue(), + zonedDateTime.getDayOfMonth(), + zonedDateTime.getHour(), + zonedDateTime.getMinute(), + zonedDateTime.getSecond(), + zonedDateTime.getNano(), + j$.time.ZoneId.of(zonedDateTime.getZone().getId())); + } + + public static java.time.ZonedDateTime to(j$.time.ZonedDateTime zonedDateTime) { + return zonedDateTime == null + ? null + : java.time.ZonedDateTime.of( + zonedDateTime.getYear(), + zonedDateTime.getMonthValue(), + zonedDateTime.getDayOfMonth(), + zonedDateTime.getHour(), + zonedDateTime.getMinute(), + zonedDateTime.getSecond(), + zonedDateTime.getNano(), + java.time.ZoneId.of(zonedDateTime.getZone().getId())); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiConsumerConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiConsumerConverter.java new file mode 100644 index 00000000000000..f093532aff5131 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiConsumerConverter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 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.typeadapter.java.util.function; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.BiConsumer}. + */ +@SuppressWarnings({"AndroidJdkLibsChecker", "UnnecessarilyFullyQualified", "NewApi"}) +public abstract class BiConsumerConverter { + + private BiConsumerConverter() {} + + public static j$.util.function.BiConsumer from( + final java.util.function.BiConsumer biConsumer) { + return biConsumer == null + ? null + : new j$.util.function.BiConsumer() { + @Override + public void accept(T t, U u) { + biConsumer.accept(t, u); + } + + @Override + public j$.util.function.BiConsumer andThen( + j$.util.function.BiConsumer after) { + return from(biConsumer.andThen(to(after))); + } + }; + } + + public static java.util.function.BiConsumer to( + final j$.util.function.BiConsumer biConsumer) { + return biConsumer == null + ? null + : new java.util.function.BiConsumer() { + @Override + public void accept(T t, U u) { + biConsumer.accept(t, u); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiFunctionConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiFunctionConverter.java new file mode 100644 index 00000000000000..17b16212912942 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BiFunctionConverter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 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.typeadapter.java.util.function; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.BiFunction}. + */ +@SuppressWarnings({"AndroidJdkLibsChecker", "UnnecessarilyFullyQualified"}) +public abstract class BiFunctionConverter { + + private BiFunctionConverter() {} + + public static j$.util.function.BiFunction from( + final java.util.function.BiFunction function) { + return function == null + ? null + : new j$.util.function.BiFunction() { + @Override + public R apply(T t, U u) { + return function.apply(t, u); + } + + @Override + public j$.util.function.BiFunction andThen( + j$.util.function.Function after) { + return from(function.andThen(FunctionConverter.to(after))); + } + }; + } + + public static java.util.function.BiFunction to( + final j$.util.function.BiFunction function) { + return function == null + ? null + : new java.util.function.BiFunction() { + @Override + public R apply(T t, U u) { + return function.apply(t, u); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BinaryOperatorConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BinaryOperatorConverter.java new file mode 100644 index 00000000000000..c50d21796cda1e --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/BinaryOperatorConverter.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 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.typeadapter.java.util.function; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.BinaryOperator}. + */ +@SuppressWarnings({"AndroidJdkLibsChecker", "UnnecessarilyFullyQualified"}) +public abstract class BinaryOperatorConverter { + + private BinaryOperatorConverter() {} + + public static j$.util.function.BinaryOperator from( + final java.util.function.BinaryOperator function) { + return function == null + ? null + : new j$.util.function.BinaryOperator() { + @Override + public T apply(T t, T u) { + return function.apply(t, u); + } + + @Override + public j$.util.function.BiFunction andThen( + j$.util.function.Function after) { + return BiFunctionConverter.from(function.andThen(FunctionConverter.to(after))); + } + }; + } + + public static java.util.function.BinaryOperator to( + final j$.util.function.BinaryOperator function) { + return function == null + ? null + : new java.util.function.BinaryOperator() { + @Override + public T apply(T t, T u) { + return function.apply(t, u); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/ConsumerConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/ConsumerConverter.java new file mode 100644 index 00000000000000..3351bfc0fcc6a8 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/ConsumerConverter.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 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.typeadapter.java.util.function; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.Consumer}. + */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class ConsumerConverter { + + private ConsumerConverter() {} + + public static j$.util.function.Consumer from( + final java.util.function.Consumer consumer) { + return consumer == null + ? null + : new j$.util.function.Consumer() { + @Override + public void accept(T t) { + consumer.accept(t); + } + + @Override + public j$.util.function.Consumer andThen(j$.util.function.Consumer after) { + return from(consumer.andThen(to(after))); + } + }; + } + + public static java.util.function.Consumer to(final j$.util.function.Consumer consumer) { + return consumer == null + ? null + : new java.util.function.Consumer() { + @Override + public void accept(T t) { + consumer.accept(t); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/DoubleUnaryOperatorConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/DoubleUnaryOperatorConverter.java new file mode 100644 index 00000000000000..7cde5a556a5b00 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/DoubleUnaryOperatorConverter.java @@ -0,0 +1,64 @@ +/* + * Copyright 2020 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.typeadapter.java.util.function; + +import j$.util.function.DoubleUnaryOperator; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.DoubleUnaryOperator}. + */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class DoubleUnaryOperatorConverter { + + private DoubleUnaryOperatorConverter() {} + + public static j$.util.function.DoubleUnaryOperator from( + final java.util.function.DoubleUnaryOperator function) { + return function == null + ? null + : new j$.util.function.DoubleUnaryOperator() { + + @Override + public double applyAsDouble(double operand) { + return function.applyAsDouble(operand); + } + + @Override + public DoubleUnaryOperator compose(DoubleUnaryOperator before) { + return from(function.compose(to(before))); + } + + @Override + public DoubleUnaryOperator andThen(DoubleUnaryOperator after) { + return from(function.andThen(to(after))); + } + }; + } + + public static java.util.function.DoubleUnaryOperator to( + final j$.util.function.DoubleUnaryOperator function) { + return function == null + ? null + : new java.util.function.DoubleUnaryOperator() { + @Override + public double applyAsDouble(double operand) { + return function.applyAsDouble(operand); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/FunctionConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/FunctionConverter.java new file mode 100644 index 00000000000000..77fc3d8925ffa5 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/FunctionConverter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 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.typeadapter.java.util.function; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.Consumer}. + */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class FunctionConverter { + + private FunctionConverter() {} + + public static j$.util.function.Function from( + final java.util.function.Function function) { + return function == null + ? null + : new j$.util.function.Function() { + @Override + public R apply(T t) { + return function.apply(t); + } + + @Override + public j$.util.function.Function compose( + j$.util.function.Function before) { + return from(function.compose(to(before))); + } + + @Override + public j$.util.function.Function andThen( + j$.util.function.Function after) { + return from(function.andThen(to(after))); + } + }; + } + + public static java.util.function.Function to( + final j$.util.function.Function function) { + return function == null + ? null + : new java.util.function.Function() { + @Override + public R apply(T t) { + return function.apply(t); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntConsumerConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntConsumerConverter.java new file mode 100644 index 00000000000000..ce6cdf36e50dda --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntConsumerConverter.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 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.typeadapter.java.util.function; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.IntConsumer}. + */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class IntConsumerConverter { + + private IntConsumerConverter() {} + + public static j$.util.function.IntConsumer from(final java.util.function.IntConsumer consumer) { + return consumer == null + ? null + : new j$.util.function.IntConsumer() { + + @Override + public void accept(int value) { + consumer.accept(value); + } + + @Override + public j$.util.function.IntConsumer andThen(j$.util.function.IntConsumer after) { + return from(consumer.andThen(to(after))); + } + }; + } + + public static java.util.function.IntConsumer to(final j$.util.function.IntConsumer consumer) { + return consumer == null + ? null + : new java.util.function.IntConsumer() { + @Override + public void accept(int value) { + consumer.accept(value); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntFunctionConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntFunctionConverter.java new file mode 100644 index 00000000000000..66dbbc11387a00 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/IntFunctionConverter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020 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.typeadapter.java.util.function; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.IntFunction}. + */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class IntFunctionConverter { + + private IntFunctionConverter() {} + + public static j$.util.function.IntFunction from( + final java.util.function.IntFunction function) { + return function == null + ? null + : new j$.util.function.IntFunction() { + @Override + public R apply(int value) { + return function.apply(value); + } + }; + } + + public static java.util.function.IntFunction to( + final j$.util.function.IntFunction function) { + return function == null + ? null + : new java.util.function.IntFunction() { + @Override + public R apply(int value) { + return function.apply(value); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/PredicateConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/PredicateConverter.java new file mode 100644 index 00000000000000..4db24261b765a8 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/PredicateConverter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 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.typeadapter.java.util.function; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.Predicate}. + */ +@SuppressWarnings("AndroidJdkLibsChecker") +public abstract class PredicateConverter { + + private PredicateConverter() {} + + public static j$.util.function.Predicate from( + final java.util.function.Predicate predicate) { + return predicate == null + ? null + : new j$.util.function.Predicate() { + @Override + public boolean test(T t) { + return predicate.test(t); + } + + @Override + public j$.util.function.Predicate and(j$.util.function.Predicate other) { + return from(predicate.and(to(other))); + } + + @Override + public j$.util.function.Predicate negate() { + return from(predicate.negate()); + } + + @Override + public j$.util.function.Predicate or(j$.util.function.Predicate other) { + return from(predicate.or(to(other))); + } + }; + } + + public static java.util.function.Predicate to( + final j$.util.function.Predicate predicate) { + return predicate == null + ? null + : new java.util.function.Predicate() { + @Override + public boolean test(T t) { + return predicate.test(t); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/UnaryOperatorConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/UnaryOperatorConverter.java new file mode 100644 index 00000000000000..2869878a2d9632 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeadapter/java/util/function/UnaryOperatorConverter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 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.typeadapter.java.util.function; + +/** + * Converts types between the desugar-mirrored and desugar-shadowed {@link + * java.util.function.UnaryOperator}. + */ +@SuppressWarnings({"AndroidJdkLibsChecker", "UnnecessarilyFullyQualified"}) +public abstract class UnaryOperatorConverter { + + private UnaryOperatorConverter() {} + + public static j$.util.function.UnaryOperator from( + final java.util.function.UnaryOperator unaryOperator) { + return unaryOperator == null + ? null + : new j$.util.function.UnaryOperator() { + @Override + public T apply(T t) { + return unaryOperator.apply(t); + } + + @Override + public j$.util.function.Function compose( + j$.util.function.Function before) { + return FunctionConverter.from(unaryOperator.compose(FunctionConverter.to(before))); + } + + @Override + public j$.util.function.Function andThen( + j$.util.function.Function after) { + return FunctionConverter.from(unaryOperator.andThen(FunctionConverter.to(after))); + } + }; + } + + public static java.util.function.UnaryOperator to( + final j$.util.function.UnaryOperator unaryOperator) { + return unaryOperator == null + ? null + : new java.util.function.UnaryOperator() { + @Override + public T apply(T t) { + return unaryOperator.apply(t); + } + }; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation/BUILD new file mode 100644 index 00000000000000..01b7825822c929 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation/BUILD @@ -0,0 +1,28 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +java_library( + name = "typeannotation", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation/LocalTypeAnnotationUse.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation/LocalTypeAnnotationUse.java new file mode 100644 index 00000000000000..31af36c4115fd2 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation/LocalTypeAnnotationUse.java @@ -0,0 +1,73 @@ +/* + * Copyright 2020 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.typeannotation; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.TypePath; + +/** Desugars bytecode instructions with references to type annotations. */ +public class LocalTypeAnnotationUse extends ClassVisitor { + + public LocalTypeAnnotationUse(ClassVisitor classVisitor) { + super(Opcodes.ASM9, classVisitor); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return mv == null ? null : new LocalTypeAnnotationUseMethodVisitor(api, mv); + } + + private static class LocalTypeAnnotationUseMethodVisitor extends MethodVisitor { + + LocalTypeAnnotationUseMethodVisitor(int api, MethodVisitor methodVisitor) { + super(api, methodVisitor); + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation( + int typeRef, + TypePath typePath, + Label[] start, + Label[] end, + int[] index, + String descriptor, + boolean visible) { + // The visited annotation has to be a type annotation. A LOCAL_VARIABLE-targeted annotation + // isn't retained in bytecode, regardless of retention policy. See details at, + // https://docs.oracle.com/javase/specs/jls/se11/html/jls-9.html#jls-9.6.4.2 + return null; + } + + @Override + public AnnotationVisitor visitInsnAnnotation( + int typeRef, TypePath typePath, String descriptor, boolean visible) { + // An instruction annotation has to be a type annotation. + return null; + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation( + int typeRef, TypePath typePath, String descriptor, boolean visible) { + // An exception parameter annotation present in Javac-compiled bytecode has to be a type + // annotation. See the JLS link in visitLocalVariableAnnotation of this class. + return null; + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/BUILD new file mode 100644 index 00000000000000..63c398d9536e9d --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/BUILD @@ -0,0 +1,30 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +java_library( + name = "typehierarchy", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:flogger", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"], +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HeadlessMethodKey.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HeadlessMethodKey.java new file mode 100644 index 00000000000000..f3b29318b11885 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HeadlessMethodKey.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020 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.typehierarchy; + +import com.google.auto.value.AutoValue; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; + +/** + * The key to index a method or constructor in the same class or interface. It is equivalent to + * {@link MethodKey} with the exclusion of the declaration owner class, i.e. omitting the {@link + * MethodKey#owner()} property. + */ +@AutoValue +abstract class HeadlessMethodKey { + + /** See: {@link MethodKey#name()}. */ + abstract String name(); + + /** See: {@link MethodKey#descriptor()}. */ + abstract String descriptor(); + + /** Factory method of {@link HeadlessMethodKey}. */ + static HeadlessMethodKey create(String methodName, String descriptor) { + return new AutoValue_HeadlessMethodKey(methodName, descriptor); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalMethodKey.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalMethodKey.java new file mode 100644 index 00000000000000..e44f169871321f --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalMethodKey.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 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.typehierarchy; + +import com.google.auto.value.AutoValue; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; + +/** The key to index a method in a inheritance hierarchy. */ +@AutoValue +public abstract class HierarchicalMethodKey { + + public abstract HierarchicalTypeKey owner(); + + public abstract HeadlessMethodKey headlessMethod(); + + static HierarchicalMethodKey create(HierarchicalTypeKey owner, HeadlessMethodKey headlessMethod) { + return new AutoValue_HierarchicalMethodKey(owner, headlessMethod); + } + + public static HierarchicalMethodKey from(MethodKey methodKey) { + return create( + HierarchicalTypeKey.create(methodKey.owner()), + HeadlessMethodKey.create(methodKey.name(), methodKey.descriptor())); + } + + public final MethodKey toMethodKey() { + return MethodKey.create(owner().type(), headlessMethod().name(), headlessMethod().descriptor()); + } + + /** Resolves the method in the given type hierarchy. */ + public HierarchicalMethodQuery inTypeHierarchy(TypeHierarchy typeHierarchy) { + return typeHierarchy.query(this); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalMethodQuery.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalMethodQuery.java new file mode 100644 index 00000000000000..3e4b97b5533f52 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalMethodQuery.java @@ -0,0 +1,107 @@ +/* + * Copyright 2020 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.typehierarchy; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo; +import javax.annotation.Nullable; + +/** The entry point of a method query under the context of a type inheritance hierarchy. */ +@AutoValue +public abstract class HierarchicalMethodQuery { + + public abstract HierarchicalMethodKey method(); + + public abstract TypeHierarchy typeHierarchy(); + + static HierarchicalMethodQuery create(HierarchicalMethodKey method, TypeHierarchy typeHierarchy) { + return new AutoValue_HierarchicalMethodQuery(method, typeHierarchy); + } + + public final boolean isPresent() { + return typeHierarchy().getMethods(method().owner()).contains(method().headlessMethod()); + } + + @Memoized + @Nullable // only possible if typeHierarchy is not on type-resolution-complete mode. + MethodDeclInfo methodMetaData() { + return typeHierarchy().getMethodMetadata(method()); + } + + @Memoized + @Nullable + public HierarchicalMethodKey getFirstBaseClassMethod() { + return Iterables.getFirst(getBaseClassMethods(), null); + } + + @Memoized + public ImmutableList getBaseClassMethods() { + HeadlessMethodKey headlessMethodKey = method().headlessMethod(); + return method().owner().inTypeHierarchy(typeHierarchy()).findTransitiveSuperClasses().stream() + .map(type -> type.inTypeHierarchy(typeHierarchy())) + .filter(typeAnalysis -> typeAnalysis.hasMethod(headlessMethodKey)) + .map(typeAnalysis -> HierarchicalMethodKey.create(typeAnalysis.type(), headlessMethodKey)) + .map(method -> method.inTypeHierarchy(typeHierarchy())) + .filter(this::hasAccessTo) + .map(HierarchicalMethodQuery::method) + .collect(toImmutableList()); + } + + @Memoized + public ImmutableList getBaseInterfaceMethods() { + HeadlessMethodKey headlessMethodKey = method().headlessMethod(); + return method() + .owner() + .inTypeHierarchy(typeHierarchy()) + .findTransitiveSuperInterfaces() + .stream() + .map(type -> type.inTypeHierarchy(typeHierarchy())) + .filter(typeAnalysis -> typeAnalysis.hasMethod(headlessMethodKey)) + .map(typeAnalysis -> HierarchicalMethodKey.create(typeAnalysis.type(), headlessMethodKey)) + .collect(toImmutableList()); + } + + @Memoized + public ImmutableList getBaseMethods() { + HeadlessMethodKey headlessMethodKey = method().headlessMethod(); + return method().owner().inTypeHierarchy(typeHierarchy()).findTransitiveSuperTypes().stream() + .map(type -> type.inTypeHierarchy(typeHierarchy())) + .filter(typeAnalysis -> typeAnalysis.hasMethod(headlessMethodKey)) + .map(typeAnalysis -> HierarchicalMethodKey.create(typeAnalysis.type(), headlessMethodKey)) + .map(method -> method.inTypeHierarchy(typeHierarchy())) + .filter(this::hasAccessTo) + .map(HierarchicalMethodQuery::method) + .collect(toImmutableList()); + } + + /** Returns the java package of the enclosing class of the method. */ + public String getEnclosingPackage() { + return this.method().owner().type().getPackageName(); + } + + /** True if the other method is visible from the current method. */ + private boolean hasAccessTo(HierarchicalMethodQuery other) { + return other.methodMetaData() != null + && (!other.methodMetaData().isPackageAccess() + || this.getEnclosingPackage().equals(other.getEnclosingPackage())); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalTypeKey.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalTypeKey.java new file mode 100644 index 00000000000000..3750c31c88cb42 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalTypeKey.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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.typehierarchy; + +import com.google.auto.value.AutoValue; +import com.google.devtools.build.android.desugar.langmodel.ClassName; + +/** The key to index a class or an interface in a inheritance hierarchy. */ +@AutoValue +public abstract class HierarchicalTypeKey { + + static final HierarchicalTypeKey SENTINEL = create(ClassName.create("")); + + public abstract ClassName type(); + + static HierarchicalTypeKey create(ClassName type) { + return new AutoValue_HierarchicalTypeKey(type); + } + + /** Resolves the method in the given type hierarchy. */ + public final HierarchicalTypeQuery inTypeHierarchy(TypeHierarchy typeHierarchy) { + return typeHierarchy.query(this); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalTypeQuery.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalTypeQuery.java new file mode 100644 index 00000000000000..ffd0aae31e55a7 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/HierarchicalTypeQuery.java @@ -0,0 +1,120 @@ +/* + * Copyright 2020 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.typehierarchy; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.util.ArrayDeque; +import java.util.LinkedHashSet; +import java.util.Queue; +import javax.annotation.Nullable; + +/** + * The entry point of a class or an interface query under the context of a type inheritance + * hierarchy. + */ +@AutoValue +public abstract class HierarchicalTypeQuery { + + abstract HierarchicalTypeKey type(); + + abstract TypeHierarchy typeHierarchy(); + + public static HierarchicalTypeQuery create( + HierarchicalTypeKey type, TypeHierarchy typeHierarchy) { + return new AutoValue_HierarchicalTypeQuery(type, typeHierarchy); + } + + public final boolean hasSuperClass() { + return findDirectSuperClass() != null; + } + + @Nullable + public final HierarchicalTypeKey findDirectSuperClass() { + return typeHierarchy().getDirectSuperClass(type()); + } + + public final ImmutableSet findDirectSuperInterfaces() { + return typeHierarchy().getDirectSuperInterfaces(type()); + } + + public final ImmutableSet findDirectSuperTypes() { + HierarchicalTypeKey directSuperClass = findDirectSuperClass(); + return directSuperClass == null + ? findDirectSuperInterfaces() + : ImmutableSet.builder() + .add(directSuperClass) + .addAll(findDirectSuperInterfaces()) + .build(); + } + + public final ImmutableList findTransitiveSuperClasses() { + ImmutableList.Builder superClassesBuilder = ImmutableList.builder(); + HierarchicalTypeKey workingKey = findDirectSuperClass(); + while (workingKey != null) { + superClassesBuilder.add(workingKey); + workingKey = workingKey.inTypeHierarchy(typeHierarchy()).findDirectSuperClass(); + } + return superClassesBuilder.build(); + } + + public final ImmutableList findTransitiveSuperInterfaces() { + LinkedHashSet transitiveInterfaces = + new LinkedHashSet<>(findDirectSuperInterfaces()); + Queue workingTypes = new ArrayDeque<>(findDirectSuperTypes()); + while (!workingTypes.isEmpty()) { + HierarchicalTypeKey frontKey = workingTypes.remove(); + for (HierarchicalTypeKey superInterface : + frontKey.inTypeHierarchy(typeHierarchy()).findDirectSuperInterfaces()) { + if (transitiveInterfaces.add(superInterface)) { + workingTypes.add(superInterface); + } + } + HierarchicalTypeKey superClass = + frontKey.inTypeHierarchy(typeHierarchy()).findDirectSuperClass(); + if (superClass != null) { + workingTypes.add(superClass); + } + } + return ImmutableList.copyOf(transitiveInterfaces); + } + + public final ImmutableList findTransitiveSuperTypes() { + LinkedHashSet transitiveSuperTypes = + new LinkedHashSet<>(findDirectSuperTypes()); + Queue workingTypes = new ArrayDeque<>(findDirectSuperTypes()); + while (!workingTypes.isEmpty()) { + HierarchicalTypeKey frontKey = workingTypes.remove(); + for (HierarchicalTypeKey superType : + frontKey.inTypeHierarchy(typeHierarchy()).findDirectSuperTypes()) { + if (transitiveSuperTypes.add(superType)) { + workingTypes.add(superType); + } + } + } + return ImmutableList.copyOf(transitiveSuperTypes); + } + + public final ImmutableSet listMethods() { + return typeHierarchy().getMethods(type()); + } + + public final boolean hasMethod(HeadlessMethodKey headlessMethodKey) { + return listMethods().contains(headlessMethodKey); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchy.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchy.java new file mode 100644 index 00000000000000..b72dbf1e358b0f --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchy.java @@ -0,0 +1,139 @@ +/* + * Copyright 2020 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.typehierarchy; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import javax.annotation.Nullable; + +/** An archive for class inheritance and overridable methods and memoizers of query results. */ +@AutoValue +public abstract class TypeHierarchy { + + private final ConcurrentMap typeQueryResults = + new ConcurrentHashMap<>(); + + private final ConcurrentMap methodQueryResults = + new ConcurrentHashMap<>(); + + abstract ImmutableMap directSuperClassByType(); + + abstract ImmutableSetMultimap directInterfacesByType(); + + public abstract ImmutableSetMultimap + headlessMethodKeysByType(); + + public abstract ImmutableMap methodMetadata(); + + abstract boolean requireTypeResolutionComplete(); + + public static TypeHierarchyBuilder builder() { + return new AutoValue_TypeHierarchy.Builder(); + } + + @Nullable + final HierarchicalTypeKey getDirectSuperClass(HierarchicalTypeKey type) { + HierarchicalTypeKey superClass = directSuperClassByType().get(type); + return HierarchicalTypeKey.SENTINEL.equals(superClass) ? null : superClass; + } + + final ImmutableSet getDirectSuperInterfaces(HierarchicalTypeKey type) { + return directInterfacesByType().get(type); + } + + final ImmutableSet getMethods(HierarchicalTypeKey type) { + return headlessMethodKeysByType().get(type); + } + + final MethodDeclInfo getMethodMetadata(HierarchicalMethodKey method) { + MethodDeclInfo methodMetadata = methodMetadata().get(method); + if (requireTypeResolutionComplete()) { + checkNotNull( + methodMetadata, + "Expected method data present under type-resolution-complete mode for %s."); + } + return methodMetadata; + } + + final HierarchicalTypeQuery query(HierarchicalTypeKey type) { + return typeQueryResults.computeIfAbsent(type, this::createQuery); + } + + final HierarchicalMethodQuery query(HierarchicalMethodKey method) { + return methodQueryResults.computeIfAbsent(method, this::createQuery); + } + + private HierarchicalTypeQuery createQuery(HierarchicalTypeKey type) { + return HierarchicalTypeQuery.create(type, this); + } + + private HierarchicalMethodQuery createQuery(HierarchicalMethodKey method) { + return HierarchicalMethodQuery.create(method, this); + } + + @AutoValue.Builder + abstract static class TypeHierarchyBuilder { + + abstract ImmutableMap.Builder + directSuperClassByTypeBuilder(); + + abstract ImmutableSetMultimap.Builder + directInterfacesByTypeBuilder(); + + abstract ImmutableSetMultimap.Builder + headlessMethodKeysByTypeBuilder(); + + abstract ImmutableMap.Builder methodMetadataBuilder(); + + @CanIgnoreReturnValue + final TypeHierarchyBuilder putDirectSuperClass( + HierarchicalTypeKey declaredType, HierarchicalTypeKey superclass) { + directSuperClassByTypeBuilder().put(declaredType, superclass); + return this; + } + + @CanIgnoreReturnValue + final TypeHierarchyBuilder putDirectInterfaces( + HierarchicalTypeKey declaredType, ImmutableSet directInterfaces) { + directInterfacesByTypeBuilder().putAll(declaredType, directInterfaces); + return this; + } + + @CanIgnoreReturnValue + final TypeHierarchyBuilder putMethod(MethodDeclInfo methodDecl) { + checkState(!methodDecl.isPrivateAccess()); + HierarchicalTypeKey typeKey = HierarchicalTypeKey.create(methodDecl.owner()); + HierarchicalMethodKey methodKey = HierarchicalMethodKey.from(methodDecl.methodKey()); + headlessMethodKeysByTypeBuilder().put(typeKey, methodKey.headlessMethod()); + methodMetadataBuilder().put(methodKey, methodDecl); + return this; + } + + abstract TypeHierarchyBuilder setRequireTypeResolutionComplete(boolean value); + + abstract TypeHierarchy build(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyClassVisitor.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyClassVisitor.java new file mode 100644 index 00000000000000..7a1bc0a915018e --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyClassVisitor.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 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.typehierarchy; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableSet.toImmutableSet; + +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchy.TypeHierarchyBuilder; +import java.util.Arrays; +import javax.annotation.Nullable; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +/** Collects the type hierarchy information with ASM framework's class visitor. */ +class TypeHierarchyClassVisitor extends ClassVisitor { + + private final TypeHierarchyBuilder typeHierarchyBuilder; + private final String resourcePath; + + private HierarchicalTypeKey hierarchicalType; + private int classAccess; + + TypeHierarchyClassVisitor( + int api, + String resourcePath, + TypeHierarchyBuilder typeHierarchyBuilder, + ClassVisitor classVisitor) { + super(api, classVisitor); + this.resourcePath = resourcePath; + this.typeHierarchyBuilder = typeHierarchyBuilder; + } + + @Override + public void visit( + int version, + int access, + String name, + @Nullable String signature, + @Nullable String superName, + @Nullable String[] interfaces) { + hierarchicalType = HierarchicalTypeKey.create(ClassName.create(name)); + checkState( + resourcePath.equals(hierarchicalType.type().classFilePathName()), + "Expected resource path %s to match class name %s.", + resourcePath, + hierarchicalType); + classAccess = access; + HierarchicalTypeKey superClassType = + superName == null + ? HierarchicalTypeKey.SENTINEL + : HierarchicalTypeKey.create(ClassName.create(superName)); + typeHierarchyBuilder.putDirectSuperClass(hierarchicalType, superClassType); + ImmutableSet superInterfaceTypes = + interfaces == null + ? ImmutableSet.of() + : Arrays.stream(interfaces) + .map(ClassName::create) + .map(HierarchicalTypeKey::create) + .collect(toImmutableSet()); + typeHierarchyBuilder.putDirectInterfaces(hierarchicalType, superInterfaceTypes); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodDeclInfo methodDeclInfo = + MethodDeclInfo.create( + MethodKey.create(hierarchicalType.type(), name, descriptor), + classAccess, + access, + signature, + exceptions); + if (!methodDeclInfo.isPrivateAccess() && !methodDeclInfo.isStaticMethod()) { + typeHierarchyBuilder.putMethod(methodDeclInfo); + } + return super.visitMethod(access, name, descriptor, signature, exceptions); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyScavenger.java b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyScavenger.java new file mode 100644 index 00000000000000..0fbe528f631a39 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyScavenger.java @@ -0,0 +1,72 @@ +/* + * Copyright 2020 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.typehierarchy; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.io.FileContentProvider; +import com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchy.TypeHierarchyBuilder; +import org.objectweb.asm.Opcodes; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Collection; +import java.util.stream.Collectors; +import org.objectweb.asm.ClassReader; + +/** The public APs that collects type hierarchy information from IO operations. */ +public class TypeHierarchyScavenger { + + public static TypeHierarchy analyze( + ImmutableList> inputProviders, + boolean requireTypeResolutionComplete) { + TypeHierarchyBuilder typeHierarchyBuilder = + TypeHierarchy.builder().setRequireTypeResolutionComplete(requireTypeResolutionComplete); + for (FileContentProvider contentProvider : inputProviders) { + if (contentProvider.isClassFile()) { + try (InputStream inputStream = contentProvider.get()) { + ClassReader cr = new ClassReader(inputStream); + TypeHierarchyClassVisitor cv = + new TypeHierarchyClassVisitor( + Opcodes.ASM9, + contentProvider.getBinaryPathName(), + typeHierarchyBuilder, + null); + cr.accept(cv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG); + } catch (IOException e) { + throw new IOError(e); + } + } + } + return typeHierarchyBuilder.build(); + } + + public static TypeHierarchy analyze( + Collection jarPaths, boolean requireTypeResolutionComplete) { + ImmutableList> fileContentProviders = + FileContentProvider.fromJarsWithFiFoResolution( + jarPaths.stream() + .filter(path -> path.toString().endsWith(".jar")) + .collect(Collectors.toList()), + jarItem -> + jarItem.jarEntry().getName().endsWith(".class") + && !jarItem.jarEntry().getName().startsWith("META-INF/")); + return analyze(fileContentProviders, requireTypeResolutionComplete); + } + + private TypeHierarchyScavenger() {} +} From 1d1e087a014232ba1be0e9885c89832f5c761c6b Mon Sep 17 00:00:00 2001 From: Alex Humesky Date: Tue, 11 Jul 2023 19:17:34 -0400 Subject: [PATCH 3/6] Revert "Clean Classic Desugar Source Code - Production - 1 / 2" This reverts commit 21efecd344dff3ae739957d89d983b45d75666ba. --- .../devtools/build/android/desugar/BUILD | 51 +- .../desugar/testing/junit/AsmNode.java | 72 +++ .../build/android/desugar/testing/junit/BUILD | 50 ++ .../desugar/testing/junit/DesugarRule.java | 120 ++++ .../testing/junit/DesugarRuleBuilder.java | 396 +++++++++++++ .../desugar/testing/junit/DesugarRunner.java | 278 +++++++++ .../testing/junit/DesugarTestHelpers.java | 87 +++ .../testing/junit/DynamicClassLiteral.java | 62 ++ .../desugar/testing/junit/ErrorMessenger.java | 48 ++ .../junit/FromParameterValueSource.java | 33 ++ .../junit/JarTransformationRecord.java | 89 +++ .../desugar/testing/junit/JdkSuppress.java | 42 ++ .../desugar/testing/junit/JdkVersion.java | 61 ++ .../testing/junit/ParameterValueSource.java | 49 ++ .../junit/ParameterValueSourceSet.java | 31 + .../testing/junit/RuntimeEntityResolver.java | 539 ++++++++++++++++++ .../testing/junit/RuntimeJarEntry.java | 61 ++ .../testing/junit/RuntimeMethodHandle.java | 88 +++ .../testing/junit/SourceCompilationUnit.java | 126 ++++ .../junit/ValueSourceAnnotatedMethod.java | 124 ++++ 20 files changed, 2406 insertions(+), 1 deletion(-) create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/AsmNode.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/BUILD create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRule.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleBuilder.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRunner.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarTestHelpers.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DynamicClassLiteral.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ErrorMessenger.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/FromParameterValueSource.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JarTransformationRecord.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JdkSuppress.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JdkVersion.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ParameterValueSource.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ParameterValueSourceSet.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeEntityResolver.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeJarEntry.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeMethodHandle.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/SourceCompilationUnit.java create mode 100644 src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ValueSourceAnnotatedMethod.java diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD index 53c5258cca3a09..659278af646caf 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD @@ -1,6 +1,8 @@ # Description: # Tool for desugaring Java constructs not supported by Android tools or devices. +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + filegroup( name = "embedded_tools", srcs = ["BUILD.tools"], @@ -11,16 +13,62 @@ filegroup( java_library( name = "desugar", + srcs = glob(["*.java"]), visibility = [ "//src/test/java/com/google/devtools/build/android/desugar:__pkg__", "//src/tools/android/java/com/google/devtools/build/android:__pkg__", "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:__pkg__", ], - exports = [ + runtime_deps = [ "//src/tools/android/java/com/google/devtools/build/android/desugar/dependencies", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:primitives", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:string_concats", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension", + ], + deps = [ + "//src/main/java/com/google/devtools/common/options", + "//src/main/protobuf:worker_protocol_java_proto", + "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib", + "//src/tools/android/java/com/google/devtools/build/android/desugar/config", + "//src/tools/android/java/com/google/devtools/build/android/desugar/corelibadapter", + "//src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn", "//src/tools/android/java/com/google/devtools/build/android/desugar/io", "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//src/tools/android/java/com/google/devtools/build/android/desugar/nest", + "//src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis", + "//src/tools/android/java/com/google/devtools/build/android/desugar/retarget", + "//src/tools/android/java/com/google/devtools/build/android/desugar/retarget:retarget_java_proto", + "//src/tools/android/java/com/google/devtools/build/android/desugar/strconcat", + "//src/tools/android/java/com/google/devtools/build/android/desugar/typeannotation", + "//src/tools/android/java/com/google/devtools/build/android/desugar/typehierarchy", + "//src/tools/android/java/com/google/devtools/build/android/r8:deps_collector_api", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:flogger", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +sh_binary( + name = "Desugar", + srcs = ["desugar.sh"], + data = [":Desugar_java"], + visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__pkg__", + ], + deps = ["@bazel_tools//tools/bash/runfiles"], +) + +java_binary( + name = "Desugar_java", + main_class = "com.google.devtools.build.android.desugar.Desugar", + visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__pkg__", ], + runtime_deps = [":desugar"], ) filegroup( @@ -40,6 +88,7 @@ filegroup( "//src/tools/android/java/com/google/devtools/build/android/desugar/strconcat:srcs", "//src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis:srcs", "//src/tools/android/java/com/google/devtools/build/android/desugar/scan:srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:srcs", ], visibility = ["//src/tools/android/java/com/google/devtools/build/android:__pkg__"], ) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/AsmNode.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/AsmNode.java new file mode 100644 index 00000000000000..e76ecbc95eb049 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/AsmNode.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 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.testing.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; +import org.objectweb.asm.tree.ClassNode; + +/** + * Identifies injectable ASM node fields (e.g. {@link org.objectweb.asm.tree.ClassNode}, {@link + * org.objectweb.asm.tree.MethodNode}, {@link org.objectweb.asm.tree.FieldNode}) with a qualified + * class name. The desugar rule resolves the requested class at runtime, parse it into a {@link + * ClassNode} and assign parsed class node to the annotated field. An injectable ASM node field may + * have any access modifier (private, package-private, protected, public). Sample usage: + * + *

    
    + * @RunWith(JUnit4.class)
    + * public class DesugarRuleTest {
    + *
    + *   @Rule
    + *   public final DesugarRule desugarRule =
    + *       DesugarRule.builder(this, MethodHandles.lookup())
    + *           .addRuntimeInputs("path/to/my_jar.jar")
    + *           .build();
    + *
    + *   @Inject
    + *   @AsmNode(className = "my.package.ClassToDesugar")
    + *   private ClassNode classToDesugarClassFile;
    + *
    + *   // ... Test methods ...
    + * }
    + * 
    + */ +@Qualifier +@Documented +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AsmNode { + + /** + * The fully-qualified class name of the class to load. The format agrees with {@link + * Class#getName}. + */ + String className(); + + /** If non-empty, load the specified class member (field or method) from the enclosing class. */ + String memberName() default ""; + + /** If non-empty, use the specified member descriptor to disambiguate overloaded methods. */ + String memberDescriptor() default ""; + + /** The round during which its associated jar is being used. */ + int round() default 1; +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/BUILD new file mode 100644 index 00000000000000..f5dca059dd710a --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/BUILD @@ -0,0 +1,50 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__", + ], +) + +java_library( + name = "desugar_rule", + srcs = glob(["*.java"]), + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-commons", + "//third_party:asm-tree", + "//third_party:auto_value", + "//third_party:flogger", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:jsr330_inject", + "//third_party:junit4", + ], +) + +filegroup( + name = "android_jar_for_testing", + srcs = select({ + # TODO(ajmichael): Use @bazel_tools//tools/android:android_jar here once it supplies runfiles. + "//external:has_androidsdk": ["@androidsdk//:platforms/android-24/android.jar"], + "//conditions:default": ["@bazel_tools//tools/android:error_message.jar"], + }), +) + +genrule( + name = "jacoco_agent_jar", + srcs = ["//third_party/java/jacoco:blaze-agent"], + outs = ["jacoco_agent.jar"], + cmd = """ + cp $(location //third_party/java/jacoco:blaze-agent) $(OUTS) + """, +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRule.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRule.java new file mode 100644 index 00000000000000..975aa015d52ce2 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRule.java @@ -0,0 +1,120 @@ +/* + * Copyright 2019 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.testing.junit; + +import com.google.auto.value.AutoAnnotation; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.android.desugar.testing.junit.SourceCompilationUnit.SourceCompilationException; +import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** A JUnit4 Rule that desugars an input jar file and load the transformed jar to JVM. */ +public final class DesugarRule implements TestRule { + + static final ClassLoader BASE_CLASS_LOADER = ClassLoader.getSystemClassLoader().getParent(); + + private final Object testInstance; + private final MethodHandles.Lookup testInstanceLookup; + + private final ImmutableList injectableFields; + private final ImmutableList sourceCompilationUnits; + private final RuntimeEntityResolver runtimeEntityResolver; + + /** + * The entry point to create a {@link DesugarRule}. + * + * @param testInstance The this reference of the JUnit test class. + * @param testInstanceLookup The lookup object from the test class, i.e. + * MethodHandles.lookup() + */ + public static DesugarRuleBuilder builder(Object testInstance, Lookup testInstanceLookup) { + return new DesugarRuleBuilder(testInstance, testInstanceLookup); + } + + DesugarRule( + Object testInstance, + Lookup testInstanceLookup, + ImmutableList injectableFields, + ImmutableList sourceCompilationUnits, + RuntimeEntityResolver runtimeEntityResolver) { + this.testInstance = testInstance; + this.testInstanceLookup = testInstanceLookup; + this.injectableFields = injectableFields; + this.sourceCompilationUnits = sourceCompilationUnits; + this.runtimeEntityResolver = runtimeEntityResolver; + } + + void compileSourceInputs() throws IOException, SourceCompilationException { + for (SourceCompilationUnit compilationUnit : sourceCompilationUnits) { + compilationUnit.compile(); + } + } + + void executeDesugarTransformation() throws Exception { + runtimeEntityResolver.executeTransformation(); + } + + @Override + public Statement apply(Statement base, Description description) { + return base; + } + + public void injectTestInstanceFields() throws Throwable { + for (Field field : injectableFields) { + MethodHandle fieldSetter = testInstanceLookup.unreflectSetter(field); + fieldSetter.invoke(testInstance, resolve(field, field.getType())); + } + } + + ImmutableMap getResolvableParameters(Method method) throws Throwable { + ImmutableMap.Builder resolvableParameterValues = ImmutableMap.builder(); + for (Parameter parameter : method.getParameters()) { + if (RuntimeEntityResolver.SUPPORTED_QUALIFIERS.stream() + .anyMatch(parameter::isAnnotationPresent)) { + resolvableParameterValues.put(parameter, resolve(parameter, parameter.getType())); + } + } + return resolvableParameterValues.build(); + } + + public T resolve(AnnotatedElement param, Class type) throws Throwable { + return runtimeEntityResolver.resolve(param, type); + } + + ImmutableMap getInputClassFileMajorVersionMap() { + try { + return runtimeEntityResolver.getInputClassFileMajorVersions(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + @AutoAnnotation + static DynamicClassLiteral createDynamicClassLiteral(String value, int round) { + return new AutoAnnotation_DesugarRule_createDynamicClassLiteral(value, round); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleBuilder.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleBuilder.java new file mode 100644 index 00000000000000..39ca7f14d4f0d5 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleBuilder.java @@ -0,0 +1,396 @@ +/* + * Copyright 2020 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.testing.junit; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.jar.JarEntry; +import javax.inject.Inject; +import javax.tools.ToolProvider; +import org.junit.runner.RunWith; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +/** The builder class for {@link DesugarRule}. */ +public class DesugarRuleBuilder { + + private static final ImmutableSet> SUPPORTED_ASM_NODE_TYPES = + ImmutableSet.of(ClassNode.class, FieldNode.class, MethodNode.class); + + private static final String ANDROID_RUNTIME_JAR_JVM_FLAG_KEY = "android_runtime_jar"; + private static final String JACOCO_AGENT_JAR_JVM_FLAG_KEY = "jacoco_agent_jar"; + private static final String DUMP_PROXY_CLASSES_JVM_FLAG_KEY = + "jdk.internal.lambda.dumpProxyClasses"; + + private final Object testInstance; + private final MethodHandles.Lookup testInstanceLookup; + private final ImmutableList injectableClassLiterals; + private final ImmutableList injectableAsmNodes; + private final ImmutableList injectableMethodHandles; + private final ImmutableList injectableJarEntries; + private String workingJavaPackage = ""; + private int maxNumOfTransformations = 1; + private final List inputs = new ArrayList<>(); + private final List sourceInputs = new ArrayList<>(); + private final List javacOptions = new ArrayList<>(); + private final List classPathEntries = new ArrayList<>(); + private final List bootClassPathEntries = new ArrayList<>(); + private final Multimap customCommandOptions = LinkedHashMultimap.create(); + private final ErrorMessenger errorMessenger = new ErrorMessenger(); + + private final String androidRuntimeJarJvmFlagValue; + private final String jacocoAgentJarJvmFlagValue; + + DesugarRuleBuilder(Object testInstance, MethodHandles.Lookup testInstanceLookup) { + this.testInstance = testInstance; + this.testInstanceLookup = testInstanceLookup; + Class testClass = testInstance.getClass(); + + androidRuntimeJarJvmFlagValue = getExplicitJvmFlagValue(ANDROID_RUNTIME_JAR_JVM_FLAG_KEY); + jacocoAgentJarJvmFlagValue = getExplicitJvmFlagValue(JACOCO_AGENT_JAR_JVM_FLAG_KEY); + + if (testClass != testInstanceLookup.lookupClass()) { + errorMessenger.addError( + "Expected testInstanceLookup has private access to (%s), but get (%s). Have you" + + " passed MethodHandles.lookup() to testInstanceLookup in test class?", + testClass, testInstanceLookup.lookupClass()); + } + if (!testClass.isAnnotationPresent(RunWith.class)) { + errorMessenger.addError( + "Expected a test instance whose class is annotated with @RunWith. %s", testClass); + } + + injectableClassLiterals = + findAllInjectableFieldsWithQualifier(testClass, DynamicClassLiteral.class); + injectableAsmNodes = findAllInjectableFieldsWithQualifier(testClass, AsmNode.class); + injectableMethodHandles = + findAllInjectableFieldsWithQualifier(testClass, RuntimeMethodHandle.class); + injectableJarEntries = findAllInjectableFieldsWithQualifier(testClass, RuntimeJarEntry.class); + } + + static ImmutableList findAllInjectableFieldsWithQualifier( + Class testClass, Class annotationClass) { + ImmutableList.Builder fields = ImmutableList.builder(); + for (Class currentClass = testClass; + currentClass != null; + currentClass = currentClass.getSuperclass()) { + for (Field field : currentClass.getDeclaredFields()) { + if (field.isAnnotationPresent(Inject.class) && field.isAnnotationPresent(annotationClass)) { + fields.add(field); + } + } + } + return fields.build(); + } + + @CanIgnoreReturnValue + public DesugarRuleBuilder setWorkingJavaPackage(String workingJavaPackage) { + this.workingJavaPackage = workingJavaPackage; + return this; + } + + @CanIgnoreReturnValue + public DesugarRuleBuilder enableIterativeTransformation(int maxNumOfTransformations) { + this.maxNumOfTransformations = maxNumOfTransformations; + return this; + } + + @CanIgnoreReturnValue + public DesugarRuleBuilder addInputs(Path... inputJars) { + for (Path path : inputJars) { + if (!path.toString().endsWith(".jar")) { + errorMessenger.addError("Expected a JAR file (*.jar): Actual (%s)", path); + } + if (!Files.exists(path)) { + errorMessenger.addError("File does not exist: %s", path); + } + inputs.add(path); + } + return this; + } + + /** Add Java source files subject to be compiled during test execution. */ + @CanIgnoreReturnValue + public DesugarRuleBuilder addSourceInputs(Path... inputSourceFiles) { + for (Path path : inputSourceFiles) { + if (!path.toString().endsWith(".java")) { + errorMessenger.addError("Expected a Java source file (*.java): Actual (%s)", path); + } + if (!Files.exists(path)) { + errorMessenger.addError("File does not exist: %s", path); + } + sourceInputs.add(path); + } + return this; + } + + /** + * Add JVM-flag-specified Java source files subject to be compiled during test execution. It is + * expected the value associated with `jvmFlagKey` to be a space-separated Strings. E.g. on the + * command line you would set it like: -Dinput_srcs="path1 path2 path3", and use + * .addSourceInputsFromJvmFlag("input_srcs"). in your test class. + */ + public DesugarRuleBuilder addSourceInputsFromJvmFlag(String jvmFlagKey) { + return addSourceInputs(DesugarTestHelpers.getRuntimePathsFromJvmFlag(jvmFlagKey)); + } + + /** + * Add JVM-flag-specified Java source files subject to be compiled during test execution. It is + * expected the value associated with `jvmFlagKey` to be a space-separated Strings. E.g. on the + * command line you would set it like: -Dinput_srcs="path1 path2 path3", and use + * .addSourceInputsFromJvmFlag("input_srcs"). in your test class. + */ + public DesugarRuleBuilder addJarInputsFromJvmFlag(String jvmFlagKey) { + return addInputs(DesugarTestHelpers.getRuntimePathsFromJvmFlag(jvmFlagKey)); + } + + /** + * Add javac options used for compilation, with the same support of `javacopts` attribute in + * java_binary rule. + */ + @CanIgnoreReturnValue + public DesugarRuleBuilder addJavacOptions(String... javacOptions) { + for (String javacOption : javacOptions) { + if (!javacOption.startsWith("-")) { + errorMessenger.addError( + "Expected javac options, e.g. `-source 11`, `-target 11`, `-parameters`, Run `javac" + + " -help` from terminal for all supported options."); + } + this.javacOptions.add(javacOption); + } + return this; + } + + @CanIgnoreReturnValue + public DesugarRuleBuilder addClasspathEntries(Path... inputJars) { + Collections.addAll(classPathEntries, inputJars); + return this; + } + + @CanIgnoreReturnValue + public DesugarRuleBuilder addBootClassPathEntries(Path... inputJars) { + Collections.addAll(bootClassPathEntries, inputJars); + return this; + } + + /** Format: --= */ + @CanIgnoreReturnValue + public DesugarRuleBuilder addCommandOptions(String key, String value) { + customCommandOptions.put(key, value); + return this; + } + + private void checkJVMOptions() { + if (Strings.isNullOrEmpty(getExplicitJvmFlagValue(DUMP_PROXY_CLASSES_JVM_FLAG_KEY))) { + errorMessenger.addError( + "Expected JVM flag: \"-D%s=$$(mktemp -d)\", but it is absent.", + DUMP_PROXY_CLASSES_JVM_FLAG_KEY); + } + + if (Strings.isNullOrEmpty(androidRuntimeJarJvmFlagValue) + || !androidRuntimeJarJvmFlagValue.endsWith(".jar") + || !Files.exists(Paths.get(androidRuntimeJarJvmFlagValue))) { + errorMessenger.addError( + "Android Runtime Jar does not exist: Please check JVM flag: -D%s='%s'", + ANDROID_RUNTIME_JAR_JVM_FLAG_KEY, androidRuntimeJarJvmFlagValue); + } + + if (Strings.isNullOrEmpty(jacocoAgentJarJvmFlagValue) + || !jacocoAgentJarJvmFlagValue.endsWith(".jar") + || !Files.exists(Paths.get(jacocoAgentJarJvmFlagValue))) { + errorMessenger.addError( + "Jacoco Agent Jar does not exist: Please check JVM flag: -D%s='%s'", + JACOCO_AGENT_JAR_JVM_FLAG_KEY, jacocoAgentJarJvmFlagValue); + } + } + + public DesugarRule build() { + checkInjectableClassLiterals(); + checkInjectableAsmNodes(); + checkInjectableMethodHandles(); + checkInjectableJarEntries(); + + if (maxNumOfTransformations > 0) { + checkJVMOptions(); + if (bootClassPathEntries.isEmpty() + && !customCommandOptions.containsKey("allow_empty_bootclasspath")) { + addBootClassPathEntries(Paths.get(androidRuntimeJarJvmFlagValue)); + } + addClasspathEntries(Paths.get(jacocoAgentJarJvmFlagValue)); + } + + ImmutableList.Builder sourceCompilationUnits = ImmutableList.builder(); + if (!sourceInputs.isEmpty()) { + try { + Path runtimeCompiledJar = Files.createTempFile("runtime_compiled_", ".jar"); + sourceCompilationUnits.add( + new SourceCompilationUnit( + ToolProvider.getSystemJavaCompiler(), + ImmutableList.copyOf(javacOptions), + ImmutableList.copyOf(sourceInputs), + runtimeCompiledJar)); + addInputs(runtimeCompiledJar); + } catch (IOException e) { + errorMessenger.addError( + "Failed to access the output jar location for compilation: %s\n%s", sourceInputs, e); + } + } + + RuntimeEntityResolver runtimeEntityResolver = + new RuntimeEntityResolver( + testInstanceLookup, + workingJavaPackage, + maxNumOfTransformations, + ImmutableList.copyOf(inputs), + ImmutableList.copyOf(classPathEntries), + ImmutableList.copyOf(bootClassPathEntries), + ImmutableListMultimap.copyOf(customCommandOptions)); + + if (errorMessenger.containsAnyError()) { + throw new IllegalStateException( + String.format( + "Invalid Desugar configurations:\n%s\n", + String.join("\n", errorMessenger.getAllMessages()))); + } + + return new DesugarRule( + testInstance, + testInstanceLookup, + ImmutableList.builder() + .addAll(injectableClassLiterals) + .addAll(injectableAsmNodes) + .addAll(injectableMethodHandles) + .addAll(injectableJarEntries) + .build(), + sourceCompilationUnits.build(), + runtimeEntityResolver); + } + + private void checkInjectableClassLiterals() { + for (Field field : injectableClassLiterals) { + if (Modifier.isStatic(field.getModifiers())) { + errorMessenger.addError("Expected to be non-static for field (%s)", field); + } + + if (field.getType() != Class.class) { + errorMessenger.addError("Expected a class literal type (Class) for field (%s)", field); + } + + DynamicClassLiteral dynamicClassLiteralAnnotation = + field.getDeclaredAnnotation(DynamicClassLiteral.class); + int round = dynamicClassLiteralAnnotation.round(); + if (round < 0 || round > maxNumOfTransformations) { + errorMessenger.addError( + "Expected the round (Actual:%d) of desugar transformation within [0, %d], where 0" + + " indicates no transformation is applied.", + round, maxNumOfTransformations); + } + } + } + + private void checkInjectableAsmNodes() { + for (Field field : injectableAsmNodes) { + if (Modifier.isStatic(field.getModifiers())) { + errorMessenger.addError("Expected to be non-static for field (%s)", field); + } + + if (!SUPPORTED_ASM_NODE_TYPES.contains(field.getType())) { + errorMessenger.addError( + "Expected @inject @AsmNode on a field in one of: (%s) but gets (%s)", + SUPPORTED_ASM_NODE_TYPES, field); + } + + AsmNode astAsmNodeInfo = field.getDeclaredAnnotation(AsmNode.class); + int round = astAsmNodeInfo.round(); + if (round < 0 || round > maxNumOfTransformations) { + errorMessenger.addError( + "Expected the round (actual:%d) of desugar transformation within [0, %d], where 0" + + " indicates no transformation is used.", + round, maxNumOfTransformations); + } + } + } + + private void checkInjectableMethodHandles() { + for (Field field : injectableMethodHandles) { + if (Modifier.isStatic(field.getModifiers())) { + errorMessenger.addError("Expected to be non-static for field (%s)", field); + } + + if (field.getType() != MethodHandle.class) { + errorMessenger.addError( + "Expected @Inject @RuntimeMethodHandle annotated on a field with type (%s), but gets" + + " (%s)", + MethodHandle.class, field); + } + + RuntimeMethodHandle methodHandleRequest = + field.getDeclaredAnnotation(RuntimeMethodHandle.class); + int round = methodHandleRequest.round(); + if (round < 0 || round > maxNumOfTransformations) { + errorMessenger.addError( + "Expected the round (actual:%d) of desugar transformation within [0, %d], where 0" + + " indicates no transformation is used.", + round, maxNumOfTransformations); + } + } + } + + private void checkInjectableJarEntries() { + for (Field field : injectableJarEntries) { + if (Modifier.isStatic(field.getModifiers())) { + errorMessenger.addError("Expected to be non-static for field (%s)", field); + } + + if (field.getType() != JarEntry.class) { + errorMessenger.addError( + "Expected a field with Type: (%s) but gets (%s)", JarEntry.class.getName(), field); + } + + RuntimeJarEntry jarEntryInfo = field.getDeclaredAnnotation(RuntimeJarEntry.class); + if (jarEntryInfo.round() < 0 || jarEntryInfo.round() > maxNumOfTransformations) { + errorMessenger.addError( + "Expected the round of desugar transformation within [0, %d], where 0 indicates no" + + " transformation is used.", + maxNumOfTransformations); + } + } + } + + private static String getExplicitJvmFlagValue(String jvmFlagKey) { + String jvmFlagValue = System.getProperty(jvmFlagKey); + return Strings.isNullOrEmpty(jvmFlagValue) ? "" : jvmFlagValue; + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRunner.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRunner.java new file mode 100644 index 00000000000000..aef4a80fb36bd7 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRunner.java @@ -0,0 +1,278 @@ +/* + * Copyright 2020 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.testing.junit; + +import static java.util.Collections.max; +import static java.util.Collections.min; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.flogger.GoogleLogger; +import com.google.devtools.build.android.desugar.testing.junit.SourceCompilationUnit.SourceCompilationException; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; + +/** + * A custom test runner that powers desugar tests. + * + *

    The test runner works the same way as {@link org.junit.runners.JUnit4}, except that, + *

  • Allows each {@link Test} method to declare injectable parameters, whose values will be + * populated at runtime through {@link DesugarRule}. + *
  • Performs a precondition check against {@link JdkSuppress} annotation if present. A test + * passes vacuously without execution, if the precondition check fails. + */ +public final class DesugarRunner extends BlockJUnit4ClassRunner { + + private static final int JAVA_RUNTIME_VERSION = JdkVersion.getJavaRuntimeVersion(); + + private static final ImmutableSet> SUPPORTED_ANNOTATIONS = + ImmutableSet.>builder() + .addAll(RuntimeEntityResolver.SUPPORTED_QUALIFIERS) + .add(FromParameterValueSource.class) + .build(); + + public DesugarRunner(Class testClassLiteral) throws InitializationError { + super(testClassLiteral); + } + + @Override + protected void validateTestMethods(List errors) { + validatePublicVoidMethodsWithInjectableArgs(Test.class, /* isStatic= */ false, errors); + } + + @Override + protected boolean isIgnored(FrameworkMethod child) { + return isJdkSuppressed(child) || super.isIgnored(child); + } + + private boolean isJdkSuppressed(FrameworkMethod child) { + JdkSuppress jdkSuppress = getJdkSuppress(child); + return jdkSuppress != null + && (JAVA_RUNTIME_VERSION < jdkSuppress.minJdkVersion() + || JAVA_RUNTIME_VERSION > jdkSuppress.maxJdkVersion()); + } + + private JdkSuppress getJdkSuppress(FrameworkMethod child) { + JdkSuppress jdkSuppress = child.getAnnotation(JdkSuppress.class); + return jdkSuppress != null ? jdkSuppress : getTestClass().getAnnotation(JdkSuppress.class); + } + + @Override + protected List computeTestMethods() { + List frameworkMethods = super.computeTestMethods(); + // Generates one method for each @ParameterValueSource instance. + ImmutableList.Builder expandedFrameworkMethods = ImmutableList.builder(); + for (FrameworkMethod frameworkMethod : frameworkMethods) { + Method reflectMethod = frameworkMethod.getMethod(); + if (reflectMethod.isAnnotationPresent(ParameterValueSourceSet.class)) { + ParameterValueSourceSet paramValues = + reflectMethod.getDeclaredAnnotation(ParameterValueSourceSet.class); + for (ParameterValueSource paramValueBundle : paramValues.value()) { + expandedFrameworkMethods.add( + ValueSourceAnnotatedMethod.create(frameworkMethod.getMethod(), paramValueBundle)); + } + } else if (reflectMethod.isAnnotationPresent(ParameterValueSource.class)) { + ParameterValueSource paramValueBundle = + reflectMethod.getDeclaredAnnotation(ParameterValueSource.class); + expandedFrameworkMethods.add( + ValueSourceAnnotatedMethod.create(frameworkMethod.getMethod(), paramValueBundle)); + } else { + expandedFrameworkMethods.add(frameworkMethod); + } + } + return expandedFrameworkMethods.build(); + } + + /** + * In replacement of {@link BlockJUnit4ClassRunner#validatePublicVoidNoArgMethods} for @Test + * method signature validation. + */ + private void validatePublicVoidMethodsWithInjectableArgs( + Class annotation, boolean isStatic, List errors) { + List methods = getTestClass().getAnnotatedMethods(annotation); + for (FrameworkMethod eachTestMethod : methods) { + eachTestMethod.validatePublicVoid(isStatic, errors); + validateInjectableParameters(eachTestMethod, errors); + validateParameterValueSource(eachTestMethod, errors); + } + } + + private static void validateInjectableParameters( + FrameworkMethod eachTestMethod, List errors) { + for (Parameter parameter : eachTestMethod.getMethod().getParameters()) { + if (Arrays.stream(parameter.getDeclaredAnnotations()) + .map(Annotation::annotationType) + .noneMatch(SUPPORTED_ANNOTATIONS::contains)) { + errors.add( + new Exception( + String.format( + "Expected the parameter (%s) in @Test method (%s) annotated with one of" + + " the supported qualifiers %s to be injectable.", + parameter, eachTestMethod, SUPPORTED_ANNOTATIONS))); + } + } + } + + private static void validateParameterValueSource( + FrameworkMethod eachTestMethod, List errors) { + Method reflectMethod = eachTestMethod.getMethod(); + long numOfParamsWithValueRequest = + Arrays.stream(reflectMethod.getParameters()) + .filter(parameter -> parameter.isAnnotationPresent(FromParameterValueSource.class)) + .count(); + if (numOfParamsWithValueRequest > 0) { + List parameterValueSources = new ArrayList<>(); + if (reflectMethod.isAnnotationPresent(ParameterValueSource.class)) { + parameterValueSources.add(reflectMethod.getDeclaredAnnotation(ParameterValueSource.class)); + } + if (reflectMethod.isAnnotationPresent(ParameterValueSourceSet.class)) { + Collections.addAll( + parameterValueSources, + reflectMethod.getDeclaredAnnotation(ParameterValueSourceSet.class).value()); + } + for (ParameterValueSource parameterValueSource : parameterValueSources) { + int valueSourceLength = parameterValueSource.value().length; + if (valueSourceLength != numOfParamsWithValueRequest) { + errors.add( + new Exception( + String.format( + "Parameter value source bundle (%s) and @FromParameterValueSource-annotated" + + " parameters in Method (%s) mismatch in length.", + parameterValueSource, eachTestMethod))); + } + } + } + } + + @Override + protected Statement methodInvoker(FrameworkMethod method, Object test) { + DesugarRule desugarRule = + Iterables.getOnlyElement( + getTestClass().getAnnotatedFieldValues(test, Rule.class, DesugarRule.class)); + + // Compile source Java files before desugar transformation. + try { + desugarRule.compileSourceInputs(); + } catch (IOException | SourceCompilationException e) { + throw new IllegalStateException(e); + } + + JdkSuppress jdkSuppress = getJdkSuppress(method); + if (jdkSuppress != null) { + ImmutableMap inputClassFileMajorVersions = + desugarRule.getInputClassFileMajorVersionMap(); + ImmutableCollection classFileVersions = inputClassFileMajorVersions.values(); + if (min(classFileVersions) < jdkSuppress.minJdkVersion() + || max(classFileVersions) > jdkSuppress.maxJdkVersion()) { + return new VacuousSuccess( + String.format( + "@Test method (%s) passed vacuously without execution: All or part of the input" + + " class file versions (%s) does not meet the declared prerequisite version" + + " range (%s) for testing.", + method, inputClassFileMajorVersions, jdkSuppress)); + } + } + + try { + desugarRule.executeDesugarTransformation(); + desugarRule.injectTestInstanceFields(); + Method reflectMethod = method.getMethod(); + + ImmutableMap.Builder parameterBindingsBuilder = ImmutableMap.builder(); + // Load bindings from annotations. + if (reflectMethod.isAnnotationPresent(ParameterValueSourceSet.class) + || reflectMethod.isAnnotationPresent(ParameterValueSource.class)) { + parameterBindingsBuilder.putAll( + ((ValueSourceAnnotatedMethod) method).getResolvableParameters()); + } + // Load bindings from desugar rule. + parameterBindingsBuilder.putAll(desugarRule.getResolvableParameters(reflectMethod)); + + ImmutableMap parameterBindings = parameterBindingsBuilder.build(); + Parameter[] parameters = reflectMethod.getParameters(); + int parameterCount = parameters.length; + Object[] resolvedParameterValues = new Object[parameterCount]; + for (int i = 0; i < parameterCount; i++) { + resolvedParameterValues[i] = parameterBindings.get(parameters[i]); + } + return new InvokeMethodWithParams(method, test, resolvedParameterValues); + } catch (Throwable throwable) { + throw new IllegalStateException(throwable); + } + } + + /** + * Used as in replacement of {@link org.junit.internal.runners.statements.InvokeMethod} for @Test + * method execution. + */ + static class InvokeMethodWithParams extends Statement { + private final FrameworkMethod testMethod; + private final Object target; + private final Object[] params; + + InvokeMethodWithParams(FrameworkMethod testMethod, Object target, Object... params) { + this.testMethod = testMethod; + this.target = target; + this.params = params; + } + + @Override + public void evaluate() throws Throwable { + testMethod.invokeExplosively(target, params); + } + } + + /** + * Used as a statement implementation indicating that a test will pass without the execution of + * test method body. + */ + static final class VacuousSuccess extends Statement { + + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + private final String reason; + + VacuousSuccess(String reason) { + this.reason = reason; + } + + @Override + public void evaluate() throws Throwable { + logger.atWarning().log("%s", reason); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ":" + reason; + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarTestHelpers.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarTestHelpers.java new file mode 100644 index 00000000000000..cd69fa4e60c0d8 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DesugarTestHelpers.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020 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.testing.junit; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MemberUseKind; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** Static utilities that facilities desugar testing. */ +public class DesugarTestHelpers { + + private DesugarTestHelpers() {} + + /** + * A helper method that reads file paths into an array from the JVM flag value associated with + * {@code jvmFlagKey}. + */ + public static Path[] getRuntimePathsFromJvmFlag(String jvmFlagKey) { + String jvmPropertyValue = + checkNotNull( + System.getProperty(jvmFlagKey), + "Expected JVM Option to be specified: -D<%s>=", + jvmFlagKey); + return Splitter.on(" ").trimResults().splitToList(jvmPropertyValue).stream() + .map(Paths::get) + .toArray(Path[]::new); + } + + /** Find all matched method invocation instructions in the given method. */ + public static ImmutableList findMethodInvocationSites( + MethodNode enclosingMethod, + String methodOwnerRegex, + String methodNameRegex, + String methodDescRegex) { + Predicate methodOwnerPredicate = Pattern.compile(methodOwnerRegex).asPredicate(); + Predicate methodNamePredicate = Pattern.compile(methodNameRegex).asPredicate(); + Predicate methodDescPredicate = Pattern.compile(methodDescRegex).asPredicate(); + return filterInstructions(enclosingMethod, AbstractInsnNode.METHOD_INSN).stream() + .map(node -> (MethodInsnNode) node) + .filter(node -> methodOwnerPredicate.test(node.owner)) + .filter(node -> methodNamePredicate.test(node.name)) + .filter(node -> methodDescPredicate.test(node.desc)) + .map( + node -> + MethodInvocationSite.builder() + .setInvocationKind(MemberUseKind.fromValue(node.getOpcode())) + .setMethod(MethodKey.create(ClassName.create(node.owner), node.name, node.desc)) + .setIsInterface(node.itf) + .build()) + .collect(toImmutableList()); + } + + public static ImmutableList filterInstructions( + MethodNode enclosingMethod, int instructionType) { + return Arrays.stream(enclosingMethod.instructions.toArray()) + .filter(node -> node.getType() == instructionType) + .collect(toImmutableList()); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DynamicClassLiteral.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DynamicClassLiteral.java new file mode 100644 index 00000000000000..6ff14a46610272 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/DynamicClassLiteral.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 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.testing.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +/** + * Identifies injectable class-literal fields with the specified class to load at runtime and assign + * to the field. An injectable class-literal field may have any access modifier (private, + * package-private, protected, public). Sample usage: + * + *
    
    + * @RunWith(JUnit4.class)
    + * public class DesugarRuleTest {
    + *
    + *   @Rule
    + *   public final DesugarRule desugarRule =
    + *       DesugarRule.builder(this, MethodHandles.lookup())
    + *           .addRuntimeInputs("path/to/my_jar.jar")
    + *           .build();
    + *   @Inject
    + *   @DynamicClassLiteral("my.package.ClassToDesugar")
    + *   private Class classToDesugarClass;
    + *
    + *   // ... Test methods ...
    + * }
    + * 
    + */ +@Qualifier +@Documented +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DynamicClassLiteral { + + /** + * The fully-qualified class name of the class to load. The format agrees with {@link + * Class#getName}. + */ + String value(); + + /** The round during which its associated jar is being used. */ + int round() default 1; +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ErrorMessenger.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ErrorMessenger.java new file mode 100644 index 00000000000000..f5d19b149e32ca --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ErrorMessenger.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 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.testing.junit; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.FormatMethod; +import java.util.ArrayList; +import java.util.List; + +/** A messenger that manages desugar configuration errors. */ +class ErrorMessenger { + + private final List errorMessages = new ArrayList<>(); + + @CanIgnoreReturnValue + @FormatMethod + ErrorMessenger addError(String recipe, Object... args) { + errorMessages.add(String.format(recipe, args)); + return this; + } + + boolean containsAnyError() { + return !errorMessages.isEmpty(); + } + + List getAllMessages() { + return errorMessages; + } + + @Override + public String toString() { + return getAllMessages().toString(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/FromParameterValueSource.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/FromParameterValueSource.java new file mode 100644 index 00000000000000..c5e3e1b1c81abd --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/FromParameterValueSource.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020 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.testing.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method parameter whose value will be derived from {@link ParameterValueSource}. + * + * @see ParameterValueSource for code examples. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface FromParameterValueSource {} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JarTransformationRecord.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JarTransformationRecord.java new file mode 100644 index 00000000000000..0a15d83c1f74a2 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JarTransformationRecord.java @@ -0,0 +1,89 @@ +/* + * Copyright 2019 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.testing.junit; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.Iterables; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** The transformation record that describes the desugaring of a jar. */ +@AutoValue +abstract class JarTransformationRecord { + + /** + * The full runtime path of a pre-transformationRecord jar. + * + * @see com.google.devtools.build.android.desugar.config.DesugarOptions#inputJars for details. + */ + abstract ImmutableList inputJars(); + + /** + * The full runtime path of a post-transformationRecord jar (deguared jar). + * + * @see com.google.devtools.build.android.desugar.config.DesugarOptions#inputJars for details. + */ + abstract ImmutableList outputJars(); + + /** @see com.google.devtools.build.android.desugar.config.DesugarOptions#classpath for details. */ + abstract ImmutableList classPathEntries(); + + /** + * @see com.google.devtools.build.android.desugar.config.DesugarOptions#bootclasspath for details. + */ + abstract ImmutableList bootClassPathEntries(); + + /** The remaining command options used for desugaring. */ + abstract ImmutableListMultimap extraCustomCommandOptions(); + + /** The factory method of this jar transformation record. */ + static JarTransformationRecord create( + ImmutableList inputJars, + ImmutableList outputJars, + ImmutableList classPathEntries, + ImmutableList bootClassPathEntries, + ImmutableListMultimap extraCustomCommandOptions) { + return new AutoValue_JarTransformationRecord( + inputJars, outputJars, classPathEntries, bootClassPathEntries, extraCustomCommandOptions); + } + + final ImmutableList getDesugarFlags() { + ImmutableList.Builder args = ImmutableList.builder(); + inputJars().forEach(path -> args.add("--input=" + path)); + outputJars().forEach(path -> args.add("--output=" + path)); + classPathEntries().forEach(path -> args.add("--classpath_entry=" + path)); + bootClassPathEntries().forEach(path -> args.add("--bootclasspath_entry=" + path)); + extraCustomCommandOptions().forEach((k, v) -> args.add("--" + k + "=" + v)); + return args.build(); + } + + @Memoized + ClassLoader getOutputClassLoader() throws MalformedURLException { + List urls = new ArrayList<>(); + for (Path path : Iterables.concat(outputJars(), classPathEntries(), bootClassPathEntries())) { + urls.add(path.toUri().toURL()); + } + return URLClassLoader.newInstance(urls.toArray(new URL[0]), DesugarRule.BASE_CLASS_LOADER); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JdkSuppress.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JdkSuppress.java new file mode 100644 index 00000000000000..dc21d8a818c18d --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JdkSuppress.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 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.testing.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a specific test class or test method requires a minimum and/or maximum JDK version + * to execute. + * + *

    A test will be ignored (passed vacuously) if the actual JDK version under test is out of the + * expected JDK version range. (inclusive). It is up to the implementer to specify the source of + * truth of the JDK version under investigation, e.g. the Java runtime environment, source code + * language level, class file major version, etc. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface JdkSuppress { + /** The minimum JDK version to execute (inclusive) */ + int minJdkVersion() default JdkVersion.V1_8; + /** The maximum JDK version to execute (inclusive) */ + int maxJdkVersion() default Integer.MAX_VALUE; +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JdkVersion.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JdkVersion.java new file mode 100644 index 00000000000000..329b94b36a8282 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/JdkVersion.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020 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.testing.junit; + +import com.google.common.base.Splitter; +import java.util.List; + +/** + * Enumeration of the currently known Java ClassFile versions. + * + *

    The minor version is stored in the 16 most significant bits, and the major version in the 16 + * least significant bits. These are the values that can be found in + * https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.1-200-B.2 + */ +public final class JdkVersion { + private static final Splitter SPLITTER_DOT = Splitter.on('.'); + private static final String JAVA_RUNTIME_VERSION_TEXT = + System.getProperty("java.runtime.version"); + + public static final int V1_1 = 3 << 16 | 45; + public static final int V1_2 = 0 << 16 | 46; + public static final int V1_3 = 0 << 16 | 47; + public static final int V1_4 = 0 << 16 | 48; + public static final int V1_5 = 0 << 16 | 49; + public static final int V1_6 = 0 << 16 | 50; + public static final int V1_7 = 0 << 16 | 51; + public static final int V1_8 = 0 << 16 | 52; + public static final int V9 = 0 << 16 | 53; + public static final int V10 = 0 << 16 | 54; + public static final int V11 = 0 << 16 | 55; + public static final int V12 = 0 << 16 | 56; + public static final int V13 = 0 << 16 | 57; + public static final int V14 = 0 << 16 | 58; + + public static int getJavaRuntimeVersion() { + if (JAVA_RUNTIME_VERSION_TEXT.startsWith("1.1.")) { + return JdkVersion.V1_1; + } + List versionSegments = SPLITTER_DOT.splitToList(JAVA_RUNTIME_VERSION_TEXT); + if (JAVA_RUNTIME_VERSION_TEXT.startsWith("1.")) { + return JdkVersion.V1_2 - 2 + Integer.parseInt(versionSegments.get(1)); + } + return JdkVersion.V9 - 9 + Integer.parseInt(versionSegments.get(0)); + } + + private JdkVersion() {} +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ParameterValueSource.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ParameterValueSource.java new file mode 100644 index 00000000000000..64767d6f685975 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ParameterValueSource.java @@ -0,0 +1,49 @@ +/* + * Copyright 2020 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.testing.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A bundle of textually-represented values, subject to parsing before populating parameters. + * + *

    Code Example: + * + *

    
    + *   @Test
    + *   @ParameterValueSource({"1", "2", "3"})
    + *   @ParameterValueSource({"200", "300", "500"})
    + *   public void twoSum(
    + *       @FromParameterValueSource int operandLeft,
    + *       @FromParameterValueSource int operandRight,
    + *       @FromParameterValueSource int expectedResult) {
    + *     assertThat(operandLeft + operandRight).isEqualTo(expectedResult);
    + *   }
    + * 
    + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Repeatable(ParameterValueSourceSet.class) +public @interface ParameterValueSource { + String[] value(); +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ParameterValueSourceSet.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ParameterValueSourceSet.java new file mode 100644 index 00000000000000..cac32176785544 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ParameterValueSourceSet.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 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.testing.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** The container for the repeatable {@link ParameterValueSource} annotation. */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ParameterValueSourceSet { + ParameterValueSource[] value(); +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeEntityResolver.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeEntityResolver.java new file mode 100644 index 00000000000000..ab4d81aa1dfd38 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeEntityResolver.java @@ -0,0 +1,539 @@ +/* + * Copyright 2020 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.testing.junit; + +import static com.google.common.base.Preconditions.checkState; +import static org.objectweb.asm.ClassReader.SKIP_CODE; +import static org.objectweb.asm.ClassReader.SKIP_DEBUG; +import static org.objectweb.asm.ClassReader.SKIP_FRAMES; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.common.collect.Table; +import com.google.devtools.build.android.desugar.Desugar; +import com.google.devtools.build.android.desugar.io.JarItem; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberKey; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.FieldKey; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle.MemberUseContext; +import org.objectweb.asm.Opcodes; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Collectors; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +/** Resolves the dependencies of fields and test method parameters under desugar testing. */ +final class RuntimeEntityResolver { + + static final ImmutableSet> SUPPORTED_QUALIFIERS = + ImmutableSet.of( + DynamicClassLiteral.class, + AsmNode.class, + RuntimeMethodHandle.class, + RuntimeJarEntry.class); + private static final String DEFAULT_OUTPUT_ROOT_PREFIX = "desugared_dump"; + + private final MethodHandles.Lookup testInstanceLookup; + private final String workingJavaPackage; + private final int maxNumOfTransformations; + private final ImmutableList inputs; + private final ImmutableList classPathEntries; + private final ImmutableList bootClassPathEntries; + private final ImmutableMultimap customCommandOptions; + + private final List jarTransformationRecords; + private ClassLoader inputClassLoader; + + /** The state of the already-created directories to avoid directory re-creation. */ + private final Map tempDirs = new HashMap<>(); + + /** A table for the lookup of missing user-supplied class member descriptors. */ + private final Table< + Integer, // Desugar round + ClassMemberKey, // A class member without descriptor (empty descriptor string). + Set>> // The set of same-name class members with their descriptors. + descriptorLookupRepo = HashBasedTable.create(); + + /** + * A table for the lookup of reflection-based class member representation from round and class + * member key. + */ + private final Table< + Integer, // Desugar round + ClassMemberKey, // A class member with descriptor. + java.lang.reflect.Member> // A reflection-based Member instance. + reflectionBasedMembers = HashBasedTable.create(); + + RuntimeEntityResolver( + Lookup testInstanceLookup, + String workingJavaPackage, + int maxNumOfTransformations, + ImmutableList inputs, + ImmutableList classPathEntries, + ImmutableList bootClassPathEntries, + ImmutableMultimap customCommandOptions) { + this.testInstanceLookup = testInstanceLookup; + this.workingJavaPackage = workingJavaPackage; + this.maxNumOfTransformations = maxNumOfTransformations; + this.inputs = inputs; + this.classPathEntries = classPathEntries; + this.bootClassPathEntries = bootClassPathEntries; + this.customCommandOptions = customCommandOptions; + this.jarTransformationRecords = new ArrayList<>(maxNumOfTransformations); + } + + void executeTransformation() throws Exception { + inputClassLoader = getInputClassLoader(); + ImmutableList transInputs = inputs; + for (int round = 1; round <= maxNumOfTransformations; round++) { + ImmutableList transOutputs = + getRuntimeOutputPaths( + transInputs, + tempDirs, + /* outputRootPrefix= */ DEFAULT_OUTPUT_ROOT_PREFIX + "_" + round); + JarTransformationRecord transformationRecord = + JarTransformationRecord.create( + transInputs, + transOutputs, + ImmutableList.copyOf(classPathEntries), + ImmutableList.copyOf(bootClassPathEntries), + ImmutableListMultimap.copyOf(customCommandOptions)); + Desugar.main(transformationRecord.getDesugarFlags().toArray(new String[0])); + jarTransformationRecords.add(transformationRecord); + transInputs = transOutputs; + } + } + + public T resolve(AnnotatedElement element, Class elementType) throws Throwable { + DynamicClassLiteral dynamicClassLiteralRequest = + element.getDeclaredAnnotation(DynamicClassLiteral.class); + if (dynamicClassLiteralRequest != null) { + return elementType.cast( + loadClassLiteral( + dynamicClassLiteralRequest, + jarTransformationRecords, + inputClassLoader, + reflectionBasedMembers, + descriptorLookupRepo, + workingJavaPackage)); + } + AsmNode asmNodeRequest = element.getDeclaredAnnotation(AsmNode.class); + if (asmNodeRequest != null) { + return getAsmNode( + asmNodeRequest, elementType, jarTransformationRecords, inputs, workingJavaPackage); + } + RuntimeMethodHandle runtimeMethodHandleRequest = + element.getDeclaredAnnotation(RuntimeMethodHandle.class); + if (runtimeMethodHandleRequest != null) { + return elementType.cast( + getMethodHandle( + runtimeMethodHandleRequest, + testInstanceLookup, + jarTransformationRecords, + inputClassLoader, + reflectionBasedMembers, + descriptorLookupRepo, + workingJavaPackage)); + } + RuntimeJarEntry runtimeJarEntry = element.getDeclaredAnnotation(RuntimeJarEntry.class); + if (runtimeJarEntry != null) { + return elementType.cast( + getJarEntry(runtimeJarEntry, jarTransformationRecords, inputs, workingJavaPackage)); + } + throw new UnsupportedOperationException( + "Expected one of the supported types for injection: " + SUPPORTED_QUALIFIERS); + } + + private static void fillMissingClassMemberDescriptorRepo( + int round, + Class classLiteral, + Table, Set>> missingDescriptorLookupRepo) { + ClassName owner = ClassName.create(classLiteral); + for (Constructor constructor : classLiteral.getDeclaredConstructors()) { + ClassMemberKey memberKeyWithoutDescriptor = MethodKey.create(owner, "", ""); + ClassMemberKey memberKeyWithDescriptor = + MethodKey.create(owner, "", Type.getConstructorDescriptor(constructor)); + if (missingDescriptorLookupRepo.contains(round, memberKeyWithoutDescriptor)) { + missingDescriptorLookupRepo + .get(round, memberKeyWithoutDescriptor) + .add(memberKeyWithDescriptor); + } else { + missingDescriptorLookupRepo.put( + round, memberKeyWithoutDescriptor, Sets.newHashSet(memberKeyWithDescriptor)); + } + } + for (Method method : classLiteral.getDeclaredMethods()) { + ClassMemberKey memberKeyWithoutDescriptor = MethodKey.create(owner, method.getName(), ""); + ClassMemberKey memberKeyWithDescriptor = + MethodKey.create(owner, method.getName(), Type.getMethodDescriptor(method)); + if (missingDescriptorLookupRepo.contains(round, memberKeyWithoutDescriptor)) { + missingDescriptorLookupRepo + .get(round, memberKeyWithoutDescriptor) + .add(memberKeyWithDescriptor); + } else { + missingDescriptorLookupRepo.put( + round, memberKeyWithoutDescriptor, Sets.newHashSet(memberKeyWithDescriptor)); + } + } + for (Field field : classLiteral.getDeclaredFields()) { + ClassMemberKey memberKeyWithoutDescriptor = FieldKey.create(owner, field.getName(), ""); + ClassMemberKey memberKeyWithDescriptor = + FieldKey.create(owner, field.getName(), Type.getDescriptor(field.getType())); + if (missingDescriptorLookupRepo.contains(round, memberKeyWithoutDescriptor)) { + missingDescriptorLookupRepo + .get(round, memberKeyWithoutDescriptor) + .add(memberKeyWithDescriptor); + } else { + missingDescriptorLookupRepo.put( + round, memberKeyWithoutDescriptor, Sets.newHashSet(memberKeyWithDescriptor)); + } + } + } + + private static ImmutableTable, Member> getReflectionBasedClassMembers( + int round, Class classLiteral) { + ImmutableTable.Builder, Member> reflectionBasedMembers = + ImmutableTable.builder(); + ClassName owner = ClassName.create(classLiteral); + for (Field field : classLiteral.getDeclaredFields()) { + reflectionBasedMembers.put( + round, + FieldKey.create(owner, field.getName(), Type.getDescriptor(field.getType())), + field); + } + for (Constructor constructor : classLiteral.getDeclaredConstructors()) { + reflectionBasedMembers.put( + round, + MethodKey.create(owner, "", Type.getConstructorDescriptor(constructor)), + constructor); + } + for (Method method : classLiteral.getDeclaredMethods()) { + reflectionBasedMembers.put( + round, + MethodKey.create(owner, method.getName(), Type.getMethodDescriptor(method)), + method); + } + return reflectionBasedMembers.buildOrThrow(); + } + + private static Class loadClassLiteral( + DynamicClassLiteral dynamicClassLiteralRequest, + List jarTransformationRecords, + ClassLoader initialInputClassLoader, + Table, Member> reflectionBasedMembers, + Table, Set>> missingDescriptorLookupRepo, + String workingJavaPackage) + throws Throwable { + int round = dynamicClassLiteralRequest.round(); + ClassLoader outputJarClassLoader = + round == 0 + ? initialInputClassLoader + : jarTransformationRecords.get(round - 1).getOutputClassLoader(); + String requestedClassName = dynamicClassLiteralRequest.value(); + String qualifiedClassName = + workingJavaPackage.isEmpty() || requestedClassName.contains(".") + ? requestedClassName + : workingJavaPackage + "." + requestedClassName; + Class classLiteral = outputJarClassLoader.loadClass(qualifiedClassName); + reflectionBasedMembers.putAll(getReflectionBasedClassMembers(round, classLiteral)); + fillMissingClassMemberDescriptorRepo(round, classLiteral, missingDescriptorLookupRepo); + return classLiteral; + } + + private static T getAsmNode( + AsmNode asmNodeRequest, + Class requestedNodeType, + List jarTransformationRecords, + ImmutableList initialInputs, + String workingJavaPackage) + throws IOException, ClassNotFoundException { + String requestedClassName = asmNodeRequest.className(); + String qualifiedClassName = + workingJavaPackage.isEmpty() || requestedClassName.contains(".") + ? requestedClassName + : workingJavaPackage + "." + requestedClassName; + String classFileName = qualifiedClassName.replace('.', '/') + ".class"; + int round = asmNodeRequest.round(); + ImmutableList jars = + round == 0 ? initialInputs : jarTransformationRecords.get(round - 1).outputJars(); + ClassNode classNode = findClassNode(classFileName, jars); + if (requestedNodeType == ClassNode.class) { + return requestedNodeType.cast(classNode); + } + + String memberName = asmNodeRequest.memberName(); + String memberDescriptor = asmNodeRequest.memberDescriptor(); + if (requestedNodeType == FieldNode.class) { + return requestedNodeType.cast(getFieldNode(classNode, memberName, memberDescriptor)); + } + if (requestedNodeType == MethodNode.class) { + return requestedNodeType.cast(getMethodNode(classNode, memberName, memberDescriptor)); + } + + throw new UnsupportedOperationException( + String.format("Injecting a node type (%s) is not supported", requestedNodeType)); + } + + private static MethodHandle getMethodHandle( + RuntimeMethodHandle methodHandleRequest, + Lookup lookup, + List jarTransformationRecords, + ClassLoader initialInputClassLoader, + Table, java.lang.reflect.Member> reflectionBasedMembers, + Table, Set>> missingDescriptorLookupRepo, + String workingJavaPackage) + throws Throwable { + int round = methodHandleRequest.round(); + Class classLiteral = + loadClassLiteral( + DesugarRule.createDynamicClassLiteral(methodHandleRequest.className(), round), + jarTransformationRecords, + initialInputClassLoader, + reflectionBasedMembers, + missingDescriptorLookupRepo, + workingJavaPackage); + + ClassName owner = ClassName.create(classLiteral); + String memberName = methodHandleRequest.memberName(); + String memberDescriptor = methodHandleRequest.memberDescriptor(); + + ClassMemberKey classMemberKey = + methodHandleRequest.usage() == MemberUseContext.METHOD_INVOCATION + ? MethodKey.create(owner, memberName, memberDescriptor) + : FieldKey.create(owner, memberName, memberDescriptor); + + if (classMemberKey.descriptor().isEmpty()) { + classMemberKey = restoreMissingDescriptor(classMemberKey, round, missingDescriptorLookupRepo); + } + + switch (methodHandleRequest.usage()) { + case METHOD_INVOCATION: + return classMemberKey.isConstructor() + ? lookup.unreflectConstructor( + (Constructor) reflectionBasedMembers.get(round, classMemberKey)) + : lookup.unreflect((Method) reflectionBasedMembers.get(round, classMemberKey)); + case FIELD_GETTER: + return lookup.unreflectGetter((Field) reflectionBasedMembers.get(round, classMemberKey)); + case FIELD_SETTER: + return lookup.unreflectSetter((Field) reflectionBasedMembers.get(round, classMemberKey)); + } + + throw new AssertionError( + String.format( + "Beyond exhaustive enum values: Unexpected enum value (%s) for (Enum:%s)", + methodHandleRequest.usage(), MemberUseContext.class)); + } + + private static ClassMemberKey restoreMissingDescriptor( + ClassMemberKey classMemberKey, + int round, + Table, Set>> missingDescriptorLookupRepo) { + Set> restoredClassMemberKey = + missingDescriptorLookupRepo.get(round, classMemberKey); + if (restoredClassMemberKey == null || restoredClassMemberKey.isEmpty()) { + throw new IllegalStateException( + String.format( + "Unable to find class member (%s). Please check its presence.", classMemberKey)); + } else if (restoredClassMemberKey.size() > 1) { + throw new IllegalStateException( + String.format( + "Class Member (%s) has same-name overloaded members: (%s) \n" + + "Please specify a descriptor to disambiguate overloaded method request.", + classMemberKey, restoredClassMemberKey)); + } + return Iterables.getOnlyElement(restoredClassMemberKey); + } + + private static FieldNode getFieldNode( + ClassNode classNode, String fieldName, String fieldDescriptor) { + for (FieldNode field : classNode.fields) { + if (fieldName.equals(field.name)) { + checkState( + fieldDescriptor.isEmpty() || fieldDescriptor.equals(field.desc), + "Found name-matched field but with a different descriptor. Expected requested field" + + " descriptor, if specified, agrees with the actual field type. Field name <%s>" + + " in class <%s>; Requested Type <%s>; Actual Type: <%s>.", + fieldName, + classNode.name, + fieldDescriptor, + field.desc); + return field; + } + } + throw new IllegalStateException( + String.format("Field <%s> not found in class <%s>", fieldName, classNode.name)); + } + + private static MethodNode getMethodNode( + ClassNode classNode, String methodName, String methodDescriptor) { + boolean hasMethodDescriptor = !methodDescriptor.isEmpty(); + List matchedMethods = + classNode.methods.stream() + .filter(methodNode -> methodName.equals(methodNode.name)) + .filter(methodNode -> !hasMethodDescriptor || methodDescriptor.equals(methodNode.desc)) + .collect(Collectors.toList()); + if (matchedMethods.isEmpty()) { + throw new IllegalStateException( + String.format( + "Method is not found in class <%s>", + methodName, + hasMethodDescriptor ? ", descriptor:" + methodDescriptor : "", + classNode.name)); + } + if (matchedMethods.size() > 1) { + List matchedMethodDescriptors = + matchedMethods.stream().map(method -> method.desc).collect(Collectors.toList()); + throw new IllegalStateException( + String.format( + "Multiple matches for requested method (name: %s in class %s). Please specify the" + + " method descriptor to disambiguate overloaded method request. All descriptors" + + " of name-matched methods: %s.", + methodName, classNode.name, matchedMethodDescriptors)); + } + return Iterables.getOnlyElement(matchedMethods); + } + + private static JarItem getJarEntry( + RuntimeJarEntry jarEntryRequest, + List jarTransformationRecords, + ImmutableList initialInputs, + String workingJavaPackage) + throws IOException { + String requestedClassFile = jarEntryRequest.value(); + String jarEntryPathName = + workingJavaPackage.isEmpty() || requestedClassFile.contains("/") + ? requestedClassFile + : workingJavaPackage.replace('.', '/') + '/' + requestedClassFile; + int round = jarEntryRequest.round(); + ImmutableList jars = + round == 0 ? initialInputs : jarTransformationRecords.get(round - 1).outputJars(); + for (Path jar : jars) { + JarFile jarFile = new JarFile(jar.toFile()); + JarEntry jarEntry = jarFile.getJarEntry(jarEntryPathName); + if (jarEntry != null) { + return JarItem.create(jarFile, jarEntry); + } + } + throw new IllegalStateException( + String.format("Expected zip entry of (%s) present.", jarEntryPathName)); + } + + private static ClassNode findClassNode(String jarEntryPathName, ImmutableList jars) + throws IOException, ClassNotFoundException { + for (Path jar : jars) { + JarFile jarFile = new JarFile(jar.toFile()); + JarEntry jarEntry = jarFile.getJarEntry(jarEntryPathName); + if (jarEntry != null) { + try (InputStream inputStream = jarFile.getInputStream(jarEntry)) { + ClassReader cr = new ClassReader(inputStream); + ClassNode classNode = new ClassNode(Opcodes.ASM9); + cr.accept(classNode, 0); + return classNode; + } + } + } + throw new ClassNotFoundException(jarEntryPathName); + } + + private ClassLoader getInputClassLoader() throws MalformedURLException { + List urls = new ArrayList<>(); + for (Path path : Iterables.concat(inputs, classPathEntries, bootClassPathEntries)) { + urls.add(path.toUri().toURL()); + } + return URLClassLoader.newInstance(urls.toArray(new URL[0]), DesugarRule.BASE_CLASS_LOADER); + } + + private static ImmutableList getRuntimeOutputPaths( + ImmutableList inputs, Map tempDirs, String outputRootPrefix) + throws IOException { + ImmutableList.Builder outputRuntimePathsBuilder = ImmutableList.builder(); + for (Path path : inputs) { + String targetDirKey = Paths.get(outputRootPrefix) + "/" + path.getParent(); + final Path outputDirPath; + if (tempDirs.containsKey(targetDirKey)) { + outputDirPath = tempDirs.get(targetDirKey); + } else { + Path root = Files.createTempDirectory("junit"); + Files.delete(root); + outputDirPath = Files.createDirectories(root.resolve(Paths.get(targetDirKey))); + tempDirs.put(targetDirKey, outputDirPath); + } + outputRuntimePathsBuilder.add(outputDirPath.resolve(path.getFileName())); + } + return outputRuntimePathsBuilder.build(); + } + + ImmutableMap getInputClassFileMajorVersions() throws IOException { + return getInputClassFileMajorVersions(inputs); + } + + private static ImmutableMap getInputClassFileMajorVersions(Collection jars) + throws IOException { + ImmutableMap.Builder majorVersions = ImmutableMap.builder(); + for (Path jar : jars) { + JarFile jarFile = new JarFile(jar.toFile()); + List classFileJarEntries = + jarFile.stream() + .filter(jarEntry -> jarEntry.getName().endsWith(".class")) + .collect(Collectors.toList()); + for (JarEntry jarEntry : classFileJarEntries) { + try (InputStream inputStream = jarFile.getInputStream(jarEntry)) { + ClassReader cr = new ClassReader(inputStream); + ClassNode classNode = new ClassNode(Opcodes.ASM9); + cr.accept(classNode, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES); + majorVersions.put(classNode.name, classNode.version); + } + } + } + return majorVersions.build(); + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeJarEntry.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeJarEntry.java new file mode 100644 index 00000000000000..a5693de67f598d --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeJarEntry.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019 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.testing.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; + +/** + * Identifies injectable {@link com.google.devtools.build.android.desugar.io.JarItem} fields with a + * zip entry path. The desugar rule resolves the requested zip entry at runtime and assign it to the + * annotated field. An injectable {@link RuntimeJarEntry}-annotated field may have any access + * modifier (private, package-private, protected, public). Sample usage: + * + *
    
    + * @RunWith(JUnit4.class)
    + * public class DesugarRuleTest {
    + *
    + *   @Rule
    + *   public final DesugarRule desugarRule =
    + *       DesugarRule.builder(this, MethodHandles.lookup())
    + *           .addInputs(Paths.get("path/to/my_jar.jar"))
    + *           .build();
    + *
    + *   @Inject
    + *   @RuntimeJarEntry("my/package/ClassToDesugar.class")
    + *   private JarItem classToDesugarClassFile;
    + *
    + *   // ... Test methods ...
    + * }
    + * 
    + */ +@Qualifier +@Documented +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RuntimeJarEntry { + + /** The requested zip entry path name within a zip file. */ + String value(); + + /** The round during which its associated jar is being used. */ + int round() default 1; +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeMethodHandle.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeMethodHandle.java new file mode 100644 index 00000000000000..2a0ae275615b24 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeMethodHandle.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 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.testing.junit; + +import static com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle.MemberUseContext.METHOD_INVOCATION; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.inject.Qualifier; +import org.objectweb.asm.tree.ClassNode; + +/** + * Identifies injectable ASM node fields (e.g. {@link org.objectweb.asm.tree.ClassNode}, {@link + * org.objectweb.asm.tree.MethodNode}, {@link org.objectweb.asm.tree.FieldNode}) with a qualified + * class name. The desugar rule resolves the requested class at runtime, parses it into a {@link + * ClassNode}, and assigns the parsed class node to the annotated field. An injectable ASM node + * field may have any access modifier (private, package-private, protected, public). Sample usage: + * + *
    
    + * @RunWith(JUnit4.class)
    + * public class DesugarRuleTest {
    + *
    + *   @Rule
    + *   public final DesugarRule desugarRule =
    + *       DesugarRule.builder(this, MethodHandles.lookup())
    + *           .addRuntimeInputs("path/to/my_jar.jar")
    + *           .build();
    + *
    + *   @Inject
    + *   @RuntimeMethodHandle(
    + *       className = "my.package.ClassToDesugar",
    + *       memberName = "add",
    + *       memberDescriptor = "(II)I",
    + *       round = 2,
    + *   )
    + *   private MethodHandle adder;
    + *
    + *   // ... Test methods ...
    + * }
    + * 
    + */ +@Qualifier +@Documented +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RuntimeMethodHandle { + + /** + * The fully-qualified class name of the class to load. The format agrees with {@link + * Class#getName}. + */ + String className(); + + /** If non-empty, load the specified class member (field or method) from the enclosing class. */ + String memberName() default ""; + + /** If non-empty, use the specified member descriptor to disambiguate overloaded methods. */ + String memberDescriptor() default ""; + + MemberUseContext usage() default METHOD_INVOCATION; + + /** The round during which its associated jar is being used. */ + int round() default 1; + + /** All class member use context types. */ + enum MemberUseContext { + METHOD_INVOCATION, + FIELD_GETTER, + FIELD_SETTER, + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/SourceCompilationUnit.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/SourceCompilationUnit.java new file mode 100644 index 00000000000000..b166897ea349c8 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/SourceCompilationUnit.java @@ -0,0 +1,126 @@ +/* + * Copyright 2020 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.testing.junit; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteStreams; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.tools.JavaCompiler; + +/** Represents a compilation unit with a single jar output. */ +public final class SourceCompilationUnit { + + /** The java compiler used to compile source files. */ + private final JavaCompiler compiler; + + /** + * customJavacOptions The javac options used for compilation, with the same support of `javacopts` + * attribute in java_binary rule. + */ + private final ImmutableList customJavacOptions; + + /** The collection of source files subject to compile. */ + private final ImmutableList sourceInputs; + + /** The caller-specified write-permissible file path to the compiled jar. */ + private final Path outputJar; + + public SourceCompilationUnit( + JavaCompiler compiler, + ImmutableList customJavacOptions, + ImmutableList sourceInputs, + Path outputJar) { + this.compiler = compiler; + this.customJavacOptions = customJavacOptions; + this.sourceInputs = sourceInputs; + this.outputJar = outputJar; + } + + /** Compiles Java source files and write to the pre-specified path to the output jar. */ + Path compile() throws IOException, SourceCompilationException { + Path compilationStdOut = Files.createTempFile("compilation_stdout_", ".txt"); + Path compilationStdErr = Files.createTempFile("compilation_stderr_", ".txt"); + Path compiledRootDir = Files.createTempDirectory("compilation_prodout_"); + ImmutableList javacOptions = + ImmutableList.builder() + .addAll(customJavacOptions) + .add("-d " + compiledRootDir) + .build(); + final List compiledFiles; + try (OutputStream stdOutStream = Files.newOutputStream(compilationStdOut); + OutputStream stdErrStream = Files.newOutputStream(compilationStdErr)) { + Splitter splitter = Splitter.on(" ").trimResults().omitEmptyStrings(); + ImmutableList compilationArguments = + ImmutableList.builder() + .addAll(splitter.split(String.join(" ", javacOptions))) + .addAll(sourceInputs.stream().map(Path::toString).collect(Collectors.toList())) + .build(); + compiler.run( + nullInputStream(), + stdOutStream, + stdErrStream, + compilationArguments.toArray(new String[0])); + int maxDepth = sourceInputs.stream().mapToInt(Path::getNameCount).max().getAsInt(); + try (Stream outputStream = + Files.find(compiledRootDir, maxDepth, (path, fileAttr) -> true)) { + compiledFiles = outputStream.collect(Collectors.toList()); + } + try (JarOutputStream jarOutputStream = + new JarOutputStream(Files.newOutputStream(outputJar))) { + for (Path compiledFile : compiledFiles) { + try (InputStream inputStream = Files.newInputStream(compiledFile)) { + Path inArchivalPath = compiledRootDir.relativize(compiledFile); + JarEntry jarEntry = new JarEntry(inArchivalPath.toString()); + jarOutputStream.putNextEntry(jarEntry); + if (!Files.isDirectory(compiledFile)) { + ByteStreams.copy(inputStream, jarOutputStream); + } + jarOutputStream.closeEntry(); + } + } + } + } + String compilationStandardErrorMessage = + new String(Files.readAllBytes(compilationStdErr), Charset.defaultCharset()); + if (!compilationStandardErrorMessage.isEmpty()) { + throw new SourceCompilationException(compilationStandardErrorMessage); + } + return outputJar; + } + + private static InputStream nullInputStream() { + return new ByteArrayInputStream(new byte[] {}); + } + + static class SourceCompilationException extends Exception { + public SourceCompilationException(String message) { + super(message); + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ValueSourceAnnotatedMethod.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ValueSourceAnnotatedMethod.java new file mode 100644 index 00000000000000..4b38a1a4a8a04a --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/ValueSourceAnnotatedMethod.java @@ -0,0 +1,124 @@ +/* + * Copyright 2020 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.testing.junit; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.ImmutableMap; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.Objects; +import org.junit.runners.model.FrameworkMethod; + +/** + * Represents a {@link ParameterValueSource}-annotated method with its parameter values (either + * fully or partially) derivable from the associated value source annotation. The test runner will + * parameterize a @Test method with N {@link ParameterValueSource} annotations into N {@link + * ValueSourceAnnotatedMethod}s for testing, and provides value bindings to all {@link + * FromParameterValueSource}-annotated parameters from a {@link ParameterValueSource} instance in + * sequence. + */ +public final class ValueSourceAnnotatedMethod extends FrameworkMethod { + + /** One single bundle for parameter value resolution. */ + private final ParameterValueSource parameterValueSource; + + public static ValueSourceAnnotatedMethod create( + Method method, ParameterValueSource parameterValueSource) { + return new ValueSourceAnnotatedMethod(method, parameterValueSource); + } + + private ValueSourceAnnotatedMethod(Method method, ParameterValueSource parameterValueSource) { + super(method); + this.parameterValueSource = parameterValueSource; + } + + ImmutableMap getResolvableParameters() { + String[] providedParamValues = parameterValueSource.value(); + int i = 0; + ImmutableMap.Builder resolvableParameterValues = ImmutableMap.builder(); + for (Parameter parameter : getMethod().getParameters()) { + if (parameter.isAnnotationPresent(FromParameterValueSource.class)) { + resolvableParameterValues.put( + parameter, parsePrimitive(providedParamValues[i++], parameter.getType())); + } + } + return resolvableParameterValues.build(); + } + + @Override + public String getName() { + // Appends distinct suffixes to support test filters. + return super.getName() + "_" + Arrays.toString(parameterValueSource.value()); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ValueSourceAnnotatedMethod) { + ValueSourceAnnotatedMethod that = (ValueSourceAnnotatedMethod) obj; + return this.parameterValueSource.equals(that.parameterValueSource) + && this.getMethod().equals(that.getMethod()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(getMethod(), parameterValueSource); + } + + @Override + public String toString() { + return super.toString() + "_" + Arrays.toString(parameterValueSource.value()); + } + + private static Object parsePrimitive(String text, Class targetType) { + if (targetType == String.class) { + return text; + } + if (targetType == int.class) { + return Integer.parseInt(text); + } + if (targetType == boolean.class) { + return Boolean.parseBoolean(text); + } + if (targetType == byte.class) { + return Byte.parseByte(text); + } + if (targetType == char.class) { + checkState(text.length() == 1, "Expected a single character for char type"); + return text.charAt(0); + } + if (targetType == short.class) { + return Short.parseShort(text); + } + if (targetType == double.class) { + return Double.parseDouble(text); + } + if (targetType == float.class) { + return Float.parseFloat(text); + } + if (targetType == long.class) { + return Long.parseLong(text); + } + throw new UnsupportedOperationException("Expected a primitive type: " + text); + } +} From 40be7870a8b60ddcef7cac754a013b7d308a748c Mon Sep 17 00:00:00 2001 From: Alex Humesky Date: Tue, 11 Jul 2023 19:17:46 -0400 Subject: [PATCH 4/6] Revert "Cleanup Classic Desugar Tests" This reverts commit a8952f5691a975e03404aa506893736285f7e280. --- .../devtools/build/android/desugar/BUILD | 2170 +++++++++++++++++ .../desugar/Bug62060793TestDataGenerator.java | 372 +++ .../desugar/Bug62456849TestDataGenerator.java | 105 + .../android/desugar/ByteCodeTypePrinter.java | 242 ++ .../BytecodeTypeInferenceTest.golden.txt | 1810 ++++++++++++++ .../desugar/BytecodeTypeInferenceTest.java | 71 + .../desugar/ClassSignatureParserTest.java | 188 ++ .../desugar/CoreLibrarySupportTest.java | 528 ++++ .../desugar/CorePackageRenamerTest.java | 146 ++ .../desugar/DefaultMethodClassFixerTest.java | 246 ++ .../DesugarCoreLibraryFunctionalTest.java | 34 + .../DesugarDefaultMethodsFunctionalTest.java | 235 ++ .../desugar/DesugarFunctionalTest.java | 340 +++ .../desugar/DesugarJava8FunctionalTest.java | 426 ++++ ...rJava8LikeAndroidStudioFunctionalTest.java | 31 + .../android/desugar/DesugarLambdaTest.java | 36 + .../desugar/DesugarLongCompareTest.java | 135 + .../android/desugar/DesugarMainClassTest.java | 70 + .../DesugarObjectsRequireNonNullTest.java | 136 ++ ...DesugarTryWithResourcesFunctionalTest.java | 152 ++ .../build/android/desugar/FrameInfoTest.java | 51 + .../desugar/Java7CompatibilityTest.java | 130 + .../build/android/desugar/MethodInfoTest.java | 33 + .../android/desugar/StackMapBugTest.java | 45 + .../desugar/TryWithResourcesRewriterTest.java | 431 ++++ .../android/desugar/b72690624_testdata.jar | Bin 0 -> 3088 bytes .../capture_lambda_disassembled_golden.txt | 22 + ...ed_abstract_method_disassembled_golden.txt | 4 + ...lemented_interface_disassembled_golden.txt | 11 + .../classes_for_testing_type_inference/BUILD | 21 + .../generate_jar.sh | 25 + .../test_subjects.jar | Bin 0 -> 3956 bytes .../testsubjects/TestSubject.java | 212 ++ ...lemented_interface_disassembled_golden.txt | 17 + ..._methods_with_args_disassembled_golden.txt | 16 + .../desugar/desugar_deps_consistency_test.sh | 45 + ...synthetic_close_resource_method_golden.txt | 8 + .../devtools/build/android/desugar/diff.sh | 16 + ...ared_method_bodies_disassembled_golden.txt | 9 + ..._methods_with_args_disassembled_golden.txt | 13 + .../devtools/build/android/desugar/io/BUILD | 39 + .../android/desugar/io/FieldInfoTest.java | 33 + .../io/FileBasedTypeReferenceClosureTest.java | 137 ++ .../android/desugar/io/IndexedInputsTest.java | 128 + .../android/desugar/io/testlib/Alpha.java | 23 + .../android/desugar/io/testlib/Bravo.java | 23 + .../android/desugar/io/testlib/Charlie.java | 23 + .../android/desugar/io/testlib/Delta.java | 23 + .../android/desugar/io/testlib/Echo.java | 21 + .../testlib/ImplicitTypeReferenceSource.java | 23 + .../desugar/jacoco_0_7_5_default_method.jar | Bin 0 -> 1393 bytes ...t_method_companion_disassembled_golden.txt | 37 + .../LambdaDesugaringStackTraceTest.java | 118 + .../stacktrace/StackTraceTestTarget.java | 47 + .../build/android/desugar/langmodel/BUILD | 60 + .../desugar/langmodel/ClassMemberKeyTest.java | 118 + .../langmodel/ClassMemberRecordTest.java | 201 ++ .../langmodel/ClassMemberTrackReasonTest.java | 82 + .../android/os/Build.java | 31 + .../build/android/desugar/runtime/BUILD | 115 + .../ConcurrentWeakIdentityHashMapTest.java | 260 ++ .../desugar/runtime/StringConcatsTest.java | 40 + .../runtime/ThrowableExtensionTest.java | 453 ++++ .../ThrowableExtensionTestUtility.java | 73 + .../desugar/runtime/UnsignedIntsTest.java | 113 + .../desugar/runtime/UnsignedLongsTest.java | 111 + ...e_method_reference_disassembled_golden.txt | 22 + .../stateless_lambda_disassembled_golden.txt | 25 + ...functional_interface_should_not_execute.sh | 22 + .../desugar/testdata/CaptureLambda.java | 30 + .../testdata/ClassCallingLongCompare.java | 51 + .../testdata/ClassCallingRequireNonNull.java | 53 + .../testdata/ClassUsingTryWithResources.java | 123 + .../desugar/testdata/ConcreteFunction.java | 56 + .../testdata/ConstructorReference.java | 56 + .../android/desugar/testdata/GuavaLambda.java | 31 + .../desugar/testdata/InnerClassLambda.java | 50 + .../desugar/testdata/InterfaceWithLambda.java | 26 + .../android/desugar/testdata/Lambda.java | 57 + .../desugar/testdata/LambdaInOverride.java | 33 + .../desugar/testdata/MethodReference.java | 88 + .../testdata/MethodReferenceInSubclass.java | 37 + .../testdata/MethodReferenceSuperclass.java | 36 + .../testdata/OuterReferenceLambda.java | 30 + .../desugar/testdata/SpecializedFunction.java | 23 + .../b68049457/StaticInterfaceMethod.java | 21 + .../StaticInterfaceMethodCaller.java | 21 + .../java/lang/AutoboxedTypes.java | 36 + .../test/util/TestClassForStackMapFrame.java | 56 + ...notationsOfDefaultMethodsShouldBeKept.java | 41 + .../ConcreteDefaultInterfaceWithLambda.java | 29 + .../ConcreteOverridesDefaultWithLambda.java | 37 + ...tInterfaceMethodWithStaticInitializer.java | 174 ++ .../java8/DefaultInterfaceWithBridges.java | 66 + .../java8/DefaultInterfaceWithLambda.java | 34 + .../DefaultMethodFromSeparateJava8Target.java | 19 + ...thodFromSeparateJava8TargetOverridden.java | 25 + ...odTransitivelyFromSeparateJava8Target.java | 23 + .../java8/FunctionWithDefaultMethod.java | 78 + ...rfaceWithInitializerAndDefaultMethods.java | 60 + .../GenericDefaultInterfaceWithLambda.java | 97 + .../testdata/java8/InterfaceMethod.java | 64 + .../java8/InterfaceMethodParameters.java | 18 + .../java8/InterfaceMethodWithParam.java | 106 + .../java8/InterfaceWithDefaultMethod.java | 41 + .../java8/InterfaceWithDuplicateMethods.java | 45 + .../java8/InterfaceWithInheritedMethods.java | 44 + .../java8/Java7InterfaceWithBridges.java | 56 + .../android/desugar/testdata/java8/Named.java | 72 + .../java8/TwoInheritedDefaultMethods.java | 36 + .../testdata/java8/VisibilityTestClass.java | 22 + .../subpackage/PackagePrivateInterface.java | 33 + .../java8/subpackage/PublicInterface.java | 22 + .../testdata/separate/SeparateBaseClass.java | 30 + .../testdata/separate/SeparateInterface.java | 21 + ...ateInterfaceThatInheritsDefaultMethod.java | 18 + .../SeparateInterfaceWithDefaultMethod.java | 21 + .../android/desugar/testdata/testresource.txt | 1 + ..._desugared_core_library_jar_toc_golden.txt | 18 + ...ith_large_minsdkversion_jar_toc_golden.txt | 68 + ..._for_try_with_resources_jar_toc_golden.txt | 75 + .../desugar/testdata_desugared_jar_test.sh | 47 + .../testdata_desugared_jar_toc_golden.txt | 75 + ...estdata_desugared_java8_jar_toc_golden.txt | 161 ++ ...ithout_lambda_desugared_jar_toc_golden.txt | 38 + .../typehierarchy/TypeHierarchyTest.java | 383 +++ .../typehierarchy/testlib/ClassZulu.java | 33 + .../typehierarchy/testlib/InterfaceZulu.java | 24 + .../testlib/pkga/ClassAlpha.java | 40 + .../testlib/pkga/InterfaceAlpha.java | 26 + .../testlib/pkgb/ClassBravo.java | 34 + .../testlib/pkgb/InterfaceBravo.java | 25 + .../testlib/pkgc/ClassCharlie.java | 37 + .../testlib/pkgc/ClassPackageCharlie.java | 22 + .../testlib/pkgc/InterfaceCharlie.java | 25 + .../typehierarchy/testlib/pkgx/PieAX.java | 28 + .../typehierarchy/testlib/pkgx/PieBX.java | 25 + .../typehierarchy/testlib/pkgx/PieCX.java | 27 + .../typehierarchy/testlib/pkgy/PieAY.java | 25 + .../typehierarchy/testlib/pkgy/PieBY.java | 28 + .../typehierarchy/testlib/pkgy/PieCY.java | 24 + .../desugar/unused_closed_resource.jar | Bin 0 -> 739 bytes 142 files changed, 14518 insertions(+) create mode 100644 src/test/java/com/google/devtools/build/android/desugar/Bug62060793TestDataGenerator.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/Bug62456849TestDataGenerator.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/ClassSignatureParserTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixerTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarCoreLibraryFunctionalTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarDefaultMethodsFunctionalTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarFunctionalTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarJava8FunctionalTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarJava8LikeAndroidStudioFunctionalTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarLambdaTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarLongCompareTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarMainClassTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarObjectsRequireNonNullTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/DesugarTryWithResourcesFunctionalTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/Java7CompatibilityTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/MethodInfoTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/StackMapBugTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/b72690624_testdata.jar create mode 100644 src/test/java/com/google/devtools/build/android/desugar/capture_lambda_disassembled_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/class_with_inherited_abstract_method_disassembled_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/class_with_lambdas_in_implemented_interface_disassembled_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/BUILD create mode 100755 src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh create mode 100644 src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jar create mode 100644 src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/companion_class_with_lambdas_in_implemented_interface_disassembled_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/companion_with_static_and_default_methods_with_args_disassembled_golden.txt create mode 100755 src/test/java/com/google/devtools/build/android/desugar/desugar_deps_consistency_test.sh create mode 100644 src/test/java/com/google/devtools/build/android/desugar/desugar_unused_synthetic_close_resource_method_golden.txt create mode 100755 src/test/java/com/google/devtools/build/android/desugar/diff.sh create mode 100644 src/test/java/com/google/devtools/build/android/desugar/interface_with_desugared_method_bodies_disassembled_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/interface_with_static_and_default_methods_with_args_disassembled_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/FieldInfoTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/FileBasedTypeReferenceClosureTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/IndexedInputsTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/testlib/Alpha.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/testlib/Bravo.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/testlib/Charlie.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/testlib/Delta.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/testlib/Echo.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/io/testlib/ImplicitTypeReferenceSource.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/jacoco_0_7_5_default_method.jar create mode 100644 src/test/java/com/google/devtools/build/android/desugar/jacoco_legacy_default_method_companion_disassembled_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/lambda/LambdaDesugaringStackTraceTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/lambda/testsrc/stacktrace/StackTraceTestTarget.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/langmodel/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberKeyTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecordTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberTrackReasonTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/mocked_android_framework/android/os/Build.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/runtime/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/runtime/ConcurrentWeakIdentityHashMapTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/runtime/StringConcatsTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtensionTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtensionTestUtility.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedIntsTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongsTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/simple_instance_method_reference_disassembled_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/stateless_lambda_disassembled_golden.txt create mode 100755 src/test/java/com/google/devtools/build/android/desugar/static_initializer_of_functional_interface_should_not_execute.sh create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/CaptureLambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/ConcreteFunction.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/ConstructorReference.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/GuavaLambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/InnerClassLambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/Lambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/LambdaInOverride.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReference.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/SpecializedFunction.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/b68049457/StaticInterfaceMethod.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/b68049457/StaticInterfaceMethodCaller.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/core_library/java/lang/AutoboxedTypes.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/core_library/test/util/TestClassForStackMapFrame.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/AnnotationsOfDefaultMethodsShouldBeKept.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/ConcreteDefaultInterfaceWithLambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/ConcreteOverridesDefaultWithLambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithBridges.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithLambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8Target.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8TargetOverridden.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodTransitivelyFromSeparateJava8Target.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/FunctionalInterfaceWithInitializerAndDefaultMethods.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethod.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodParameters.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultMethod.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDuplicateMethods.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithInheritedMethods.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/Named.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/TwoInheritedDefaultMethods.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/VisibilityTestClass.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/subpackage/PackagePrivateInterface.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/java8/subpackage/PublicInterface.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/separate/SeparateBaseClass.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/separate/SeparateInterface.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/separate8/SeparateInterfaceThatInheritsDefaultMethod.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/separate8/SeparateInterfaceWithDefaultMethod.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata/testresource.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_core_library_jar_toc_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_for_disabling_twr_with_large_minsdkversion_jar_toc_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_for_try_with_resources_jar_toc_golden.txt create mode 100755 src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_jar_test.sh create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_jar_toc_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_java8_jar_toc_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_without_lambda_desugared_jar_toc_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/ClassZulu.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/InterfaceZulu.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkga/ClassAlpha.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkga/InterfaceAlpha.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgb/ClassBravo.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgb/InterfaceBravo.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/ClassCharlie.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/ClassPackageCharlie.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/InterfaceCharlie.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieAX.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieBX.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieCX.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieAY.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieBY.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieCY.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/unused_closed_resource.jar diff --git a/src/test/java/com/google/devtools/build/android/desugar/BUILD b/src/test/java/com/google/devtools/build/android/desugar/BUILD index 667b525987d502..96480813e94a69 100644 --- a/src/test/java/com/google/devtools/build/android/desugar/BUILD +++ b/src/test/java/com/google/devtools/build/android/desugar/BUILD @@ -1,3 +1,5 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_import", "java_library", "java_test") + # Description: # Tests for the Java 8 desugaring tool for Android. package( @@ -11,7 +13,2175 @@ filegroup( name = "srcs", testonly = 0, srcs = glob(["**"]) + [ + "//src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference:srcs", "//src/test/java/com/google/devtools/build/android/desugar/dependencies:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/io:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/langmodel:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/runtime:srcs", ], visibility = ["//src/test/java/com/google/devtools/build/android:__pkg__"], ) + +filegroup( + name = "android_jar_for_testing", + srcs = select({ + # TODO(ajmichael): Use @bazel_tools//tools/android:android_jar here once it supplies runfiles. + "//external:has_androidsdk": ["@androidsdk//:platforms/android-24/android.jar"], + "//conditions:default": ["@bazel_tools//tools/android:error_message.jar"], + }), +) + +java_test( + name = "DesugarFunctionalTest", + size = "small", + srcs = [ + "DesugarFunctionalTest.java", + ], + tags = ["no_windows"], + deps = [ + ":testdata_desugared", # Make tests run against desugared library + "//third_party:asm", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarIdempotencyFunctionalTest", + size = "small", + srcs = [ + "DesugarFunctionalTest.java", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarFunctionalTest", + deps = [ + ":testdata_desugared_twice", # Make tests run against twice-desugared library + "//third_party:asm", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +# Test for b/62060793. Verifies constant lambda arguments that were pushed using *CONST_0 +# instructions. +java_test( + name = "DesugarFunctionalTestForConstantArgumentsInLambdas", + size = "small", + srcs = ["DesugarLambdaTest.java"], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarLambdaTest", + deps = [ + ":desugar_lambda_with_constant_arguments_lib", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +# Test for b/62456849. If a synthetic method is named "lambda$XXX", but not used in invokedynamic, +# then Desugar should keep it in the class, rather than renaming it. +java_test( + name = "DesugarFunctionalTestForSyntheticMethodsWithLambdaNames", + size = "small", + srcs = ["DesugarFunctionalTest.java"], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarFunctionalTest", + deps = [ + ":desugar_testdata_with_synthetic_methods_with_lambda_names", + ":separate", + "//third_party:asm", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarLongCompareTest", + size = "small", + srcs = [ + "DesugarLongCompareTest.java", + ], + tags = ["no_windows"], + deps = [ + ":testdata_desugared", # Make tests run against desugared library + "//third_party:asm", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarObjectsRequireNonNullTest", + size = "small", + srcs = [ + "DesugarObjectsRequireNonNullTest.java", + ], + tags = ["no_windows"], + deps = [ + ":testdata_desugared", # Make tests run against desugared library + "//third_party:asm", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarObjectsRequireNonNullTestForAndroidLintMode", + size = "small", + srcs = ["DesugarObjectsRequireNonNullTest.java"], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarObjectsRequireNonNullTest", + deps = [ + ":desugar_testdata_by_disabling_lambda_desugaring", # Make tests run against desugared library + "//third_party:asm", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarJava8FunctionalTest", + size = "small", + srcs = [ + "DesugarFunctionalTest.java", + "DesugarJava8FunctionalTest.java", + ], + tags = ["no_windows"], + deps = [ + ":testdata_desugared_java8", # Make tests run against desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarJava8LikeAndroidStudioFunctionalTest", + size = "small", + srcs = [ + "DesugarFunctionalTest.java", + "DesugarJava8FunctionalTest.java", + "DesugarJava8LikeAndroidStudioFunctionalTest.java", + ], + tags = ["no_windows"], + deps = [ + ":testdata_desugared_java8_like_in_android_studio", # Make tests run against desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarDefaultMethodsFunctionalTest", + size = "small", + srcs = [ + "DesugarDefaultMethodsFunctionalTest.java", + "DesugarFunctionalTest.java", + "DesugarJava8FunctionalTest.java", + ], + tags = ["no_windows"], + deps = [ + ":testdata_desugared_default_methods", # Make tests run against desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarDefaultMethodsIdempotencyFunctionalTest", + size = "small", + srcs = [ + "DesugarDefaultMethodsFunctionalTest.java", + "DesugarFunctionalTest.java", + "DesugarJava8FunctionalTest.java", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarDefaultMethodsFunctionalTest", + deps = [ + ":testdata_desugared_default_methods_twice", # Make tests run against 2x desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarLikeAndroidStudioFunctionalTest", + size = "small", + srcs = [ + "DesugarFunctionalTest.java", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarFunctionalTest", + deps = [ + ":testdata_desugared_like_in_android_studio", # Make tests run against desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarFunctionalTestWithMultipleInputs", + size = "small", + srcs = [ + "DesugarFunctionalTest.java", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarFunctionalTest", + deps = [ + ":testdata_desugared_with_multiple_inputs", # Make tests run against desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarFunctionalTestFromDirectoryToJar", + size = "small", + srcs = [ + "DesugarFunctionalTest.java", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarFunctionalTest", + deps = [ + ":testdata_desugared_from_directory_to_jar", # Make tests run against desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarFunctionalTestFromDirectoryToDirectory", + size = "small", + srcs = [ + "DesugarFunctionalTest.java", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarFunctionalTest", + deps = [ + ":testdata_desugared_from_directory_to_directory", # Make tests run against desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarFunctionalTestWithClasspathDirectory", + size = "small", + srcs = [ + "DesugarFunctionalTest.java", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarFunctionalTest", + deps = [ + ":testdata_desugared_with_classpath_directory", # Make tests run against desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarCoreLibraryFunctionalTest", + size = "small", + srcs = [ + "DesugarCoreLibraryFunctionalTest.java", + ], + javacopts = ["-source 8 -target 8"], + jvm_flags = [ + # TODO (b/72181101): -Xbootclasspath/p is removed in JDK 9. + "-XX:+IgnoreUnrecognizedVMOptions", + "-Xbootclasspath/a:$(location :testdata_desugared_core_library):$(location //third_party/java/jacoco:blaze-agent)", + "--patch-module=java.base=$(location :testdata_desugared_core_library)", + ], + tags = ["no_windows"], + deps = [ + ":testdata_desugared_core_library", # Make tests run against desugared library + "//third_party:guava", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + ], +) + +java_test( + name = "StackMapBugTest", + size = "small", + srcs = [ + "StackMapBugTest.java", + ], + jvm_flags = ["-Xbootclasspath/a:$(location //third_party/java/jacoco:blaze-agent)"], + tags = ["no_windows"], + deps = [ + ":testdata_desugared_core_library", # Make tests run against desugared library + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + ], +) + +java_test( + name = "DefaultMethodClassFixerTest", + size = "small", + srcs = ["DefaultMethodClassFixerTest.java"], + data = [ + ":android_jar_for_testing", + ":separate", + ":testdata_java8", + "//third_party:guava-jars", + ], + jvm_flags = [ + "-DDefaultMethodClassFixerTest.bootclasspath=$(location :android_jar_for_testing)", + "-DDefaultMethodClassFixerTest.classpath=$(location :separate):$(location //third_party:guava-jars):$(location //third_party/java/jacoco:blaze-agent)", + "-DDefaultMethodClassFixerTest.input=$(location :testdata_java8)", + ], + tags = ["no_windows"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/r8:deps_collector_api", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + "//third_party/java/jacoco:blaze-agent", + ], +) + +java_test( + name = "MethodInfoTest", + size = "small", + srcs = ["MethodInfoTest.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "Java7CompatibilityTest", + size = "small", + srcs = ["Java7CompatibilityTest.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//third_party:asm", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "TryWithResourcesRewriterUnitTestWithReuseStrategy", + size = "small", + srcs = [ + "TryWithResourcesRewriterTest.java", + ], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=19", + "'-Dexpected.strategy=com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$ReuseDesugaringStrategy'", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.TryWithResourcesRewriterTest", + deps = [ + ":mocked_android_os_sdk_for_testing", + ":testdata", + "//src/test/java/com/google/devtools/build/android/desugar/runtime:throwable_extension_test_utility", + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "TryWithResourcesRewriterUnitTestWithMimicStrategy", + size = "small", + srcs = [ + "TryWithResourcesRewriterTest.java", + ], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=18", + "'-Dexpected.strategy=com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$MimicDesugaringStrategy'", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.TryWithResourcesRewriterTest", + deps = [ + ":mocked_android_os_sdk_for_testing", + ":testdata", + "//src/test/java/com/google/devtools/build/android/desugar/runtime:throwable_extension_test_utility", + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "TryWithResourcesRewriterUnitTestWithNullStrategy", + size = "small", + srcs = [ + "TryWithResourcesRewriterTest.java", + ], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=18", + "-Dcom.google.devtools.build.android.desugar.runtime.twr_disable_mimic=true", + "'-Dexpected.strategy=com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$NullDesugaringStrategy'", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.TryWithResourcesRewriterTest", + deps = [ + ":mocked_android_os_sdk_for_testing", + ":testdata", + "//src/test/java/com/google/devtools/build/android/desugar/runtime:throwable_extension_test_utility", + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +# This test sets the lambda dump directory via the system property in the command line +# (i.e., jvm_flags). So the dump directory is expected to succeed. +java_test( + name = "DesugarMainClassTestLambdaDirectoryCorrectlySet", + size = "small", + srcs = ["DesugarMainClassTest.java"], + jvm_flags = ["-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)"], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarMainClassTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +# Different from the test target above, this test does not set the lambda dump directory in the +# command line. Instead, it sets the system property in the test code, which is expected to fail. +java_test( + name = "DesugarMainClassTestLambdaDirectoryIncorrectlySet", + size = "small", + srcs = ["DesugarMainClassTest.java"], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarMainClassTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "BytecodeTypeInferenceTest", + srcs = ["BytecodeTypeInferenceTest.java"], + data = [ + "BytecodeTypeInferenceTest.golden.txt", + "//src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference:test_subjects", + ], + jvm_flags = [ + "-Djar_path=$(location //src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference:test_subjects)", + "-Dgolden_file=$(location BytecodeTypeInferenceTest.golden.txt)", + ], + tags = ["no_windows"], + deps = [ + ":bytecode_type_printer", + "//src/test/java/com/google/devtools/build/lib/testutil", + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +sh_test( + name = "testdata_desugared_jar_test", + size = "small", + srcs = ["testdata_desugared_jar_test.sh"], + args = [ + "$(location testdata_desugared.jar)", + "$(location testdata_desugared_jar_toc_golden.txt)", + "$(JAVABASE)", + ], + data = [ + "testdata_desugared.jar", + "testdata_desugared_jar_toc_golden.txt", # Golden file + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +# Make sure desugaring twice doesn't add any files +sh_test( + name = "testdata_desugared_twice_jar_test", + size = "small", + srcs = ["testdata_desugared_jar_test.sh"], + args = [ + "$(location testdata_desugared_twice.jar)", + "$(location testdata_desugared_jar_toc_golden.txt)", + "$(JAVABASE)", + ], + data = [ + "testdata_desugared_jar_toc_golden.txt", + "testdata_desugared_twice.jar", + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +sh_test( + name = "testdata_desugared_java8_jar_test", + size = "small", + srcs = ["testdata_desugared_jar_test.sh"], + args = [ + "$(location testdata_desugared_java8.jar)", + "$(location testdata_desugared_java8_jar_toc_golden.txt)", + "$(JAVABASE)", + ], + data = [ + "testdata_desugared_java8.jar", + "testdata_desugared_java8_jar_toc_golden.txt", # Golden file + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +# TODO(b/37110951): Add jar content test for desugared default methods that works with coverage on + +sh_test( + name = "testdata_desugared_core_library_jar_test", + size = "small", + srcs = ["testdata_desugared_jar_test.sh"], + args = [ + "$(location testdata_desugared_core_library.jar)", + "$(location testdata_desugared_core_library_jar_toc_golden.txt)", + "$(JAVABASE)", + ], + data = [ + "testdata_desugared_core_library.jar", + "testdata_desugared_core_library_jar_toc_golden.txt", # Golden file + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +sh_test( + name = "testdata_desugared_for_try_with_resources_test", + size = "small", + srcs = ["testdata_desugared_jar_test.sh"], + args = [ + "$(location testdata_desugared_for_try_with_resources.jar)", + "$(location testdata_desugared_for_try_with_resources_jar_toc_golden.txt)", + "$(JAVABASE)", + ], + data = [ + "testdata_desugared_for_try_with_resources.jar", + "testdata_desugared_for_try_with_resources_jar_toc_golden.txt", # Golden file + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +sh_test( + name = "testdata_desugared_for_try_with_resources_test_twice", + size = "small", + srcs = ["testdata_desugared_jar_test.sh"], + args = [ + "$(location testdata_desugared_for_try_with_resources_twice.jar)", + "$(location testdata_desugared_for_try_with_resources_jar_toc_golden.txt)", + "$(JAVABASE)", + ], + data = [ + "testdata_desugared_for_try_with_resources_jar_toc_golden.txt", # Golden file + "testdata_desugared_for_try_with_resources_twice.jar", + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +# Test for b/63900665 +sh_test( + name = "testdata_desugared_for_disabling_try_with_resources_with_large_minsdkversion_test", + size = "small", + srcs = ["testdata_desugared_jar_test.sh"], + args = [ + "$(location testdata_desugared_for_NO_desugaring_try_with_resources.jar)", + "$(location testdata_desugared_for_disabling_twr_with_large_minsdkversion_jar_toc_golden.txt)", + "$(JAVABASE)", + ], + data = [ + "testdata_desugared_for_NO_desugaring_try_with_resources.jar", + "testdata_desugared_for_disabling_twr_with_large_minsdkversion_jar_toc_golden.txt", # Golden file + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +# Test for b/62047432 +sh_test( + name = "initializer_of_functional_interface_should_not_execute", + size = "small", + srcs = ["static_initializer_of_functional_interface_should_not_execute.sh"], + args = [ + "$(location :desugar_testdata_with_default_methods_for_static_initializer_test) ", + ], + data = [ + ":desugar_testdata_with_default_methods_for_static_initializer_test", + ], + tags = ["no_windows"], +) + +java_library( + name = "testdata", + srcs = glob(["testdata/*.java"]), + javacopts = [ + "-Xep:SelfAssignment:OFF", + "-source 8 -target 8", + ], + resources = ["testdata/testresource.txt"], + deps = [ + ":separate", + "//third_party:guava", + ], +) + +java_library( + name = "testdata_java8", + srcs = glob([ + "testdata/*.java", + "testdata/java8/**/*.java", + ]), + javacopts = [ + "-Xep:SelfAssignment:OFF", + "-source 8 -target 8", + ], + resources = ["testdata/testresource.txt"], + deps = [ + ":separate", + ":separate_java8", + "//third_party:guava", + ], +) + +java_library( + name = "testdata_core_library", + srcs = glob([ + "testdata/core_library/**/*.java", + ]), + javacopts = ["-source 8 -target 8"], + resources = ["testdata/testresource.txt"], + deps = [ + "//third_party:error_prone", + "//third_party:guava", + ], +) + +java_library( + name = "separate", + srcs = glob(["testdata/separate/*.java"]), +) + +java_library( + name = "separate_java8", + srcs = glob(["testdata/separate8/*.java"]), +) + +java_library( + name = "testdata_like_in_android_studio", + srcs = glob(["testdata/*.java"]), + javacopts = [ + "-Xep:SelfAssignment:OFF", + "-source 8 -target 8", + ], + resources = ["testdata/testresource.txt"], + deps = [ + ":libseparate.jar", # puts full Jar instead of header Jar on the classpath + "//third_party:guava", + ], +) + +java_library( + name = "testdata_java8_like_in_android_studio", + srcs = glob([ + "testdata/*.java", + "testdata/java8/**/*.java", + ]), + javacopts = [ + "-Xep:SelfAssignment:OFF", + "-source 8 -target 8", + ], + resources = ["testdata/testresource.txt"], + deps = [ + ":libseparate.jar", # puts full Jar instead of header Jar on the classpath + ":libseparate_java8.jar", # puts full Jar instead of header Jar on the classpath + "//third_party:guava", + ], +) + +java_library( + name = "bytecode_type_printer", + srcs = ["ByteCodeTypePrinter.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:asm-util", + "//third_party:guava", + ], +) + +# The golden file for this target should NEVER contain generated lambda classes. +sh_test( + name = "desugar_testdata_by_disabling_lambda_desugaring_test", + size = "small", + srcs = ["testdata_desugared_jar_test.sh"], + args = [ + "$(location testdata_desugared_without_lambda_desugared.jar)", + "$(location testdata_desugared_without_lambda_desugared_jar_toc_golden.txt)", + "$(JAVABASE)", + ], + data = [ + "testdata_desugared_without_lambda_desugared.jar", + "testdata_desugared_without_lambda_desugared_jar_toc_golden.txt", # Golden file + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +# The following genrules run the code tested here as a build tool. While that's +# very similar to how Bazel will invoke it natively, the downside is that +# running the tested tool in a genrule doesn't contribute code coverage +# information for the tested tool. Note that the code in :testdata doesn't +# appear in coverage reports when depending on this target regardless because +# the needed metadata doesn't survive these genrules. +genrule( + name = "desugar_testdata", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--nodesugar_interface_method_bodies_if_needed -i $(location :testdata) -o $@ " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +# Generates special lambda invocation so we can test for b/62060793. +genrule( + name = "generate_lambda_with_constant_arguments_in_test_data", + outs = ["testdata_generate_lambda_with_constant_arguments.jar"], + cmd = "$(location :generate_lambda_with_constant_arguments) $@", + tags = ["no_windows"], + tools = [":generate_lambda_with_constant_arguments"], +) + +# Desugar the test data for b/62456849 +genrule( + name = "desugar_lambda_with_constant_arguments", + srcs = [ + ":generate_lambda_with_constant_arguments_in_test_data", + # Depend on Jacoco runtime in case testdata was built with coverage instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugar_generate_lambda_with_constant_arguments.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "-i $(location :generate_lambda_with_constant_arguments_in_test_data) -o $@ " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +java_import( + name = "desugar_lambda_with_constant_arguments_lib", + jars = [":desugar_lambda_with_constant_arguments"], + tags = ["no_windows"], +) + +# Convert human-written methods whose names start with "lambda$XXX" to synthetic methods, so we can +# test for b/62456849. +genrule( + name = "generate_synthetic_methods_with_lambda_names_in_test_data", + srcs = [":testdata"], + outs = ["testdata_with_generated_synthetic_methods_with_lambda_names.jar"], + cmd = "$(location :generate_synthetic_method_with_lambda_name_convention) " + + " $(location :testdata) " + + " $@ ", + tags = ["no_windows"], + tools = [":generate_synthetic_method_with_lambda_name_convention"], +) + +# Desugar the test data for b/62456849 +genrule( + name = "desugar_testdata_with_synthetic_methods_with_lambda_names", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":generate_synthetic_methods_with_lambda_names_in_test_data", + # Depend on Jacoco runtime in case testdata was built with coverage instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_with_synthetic_methods_with_lambda_names.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--nodesugar_interface_method_bodies_if_needed -i $(location :generate_synthetic_methods_with_lambda_names_in_test_data) -o $@ " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_again", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata_desugared.jar", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_twice.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--nodesugar_interface_method_bodies_if_needed -i $(location :testdata_desugared.jar) -o $@ " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +# This target tests the flag --allow_desugaring_lambda_for_lint=true, which is introduced to solve +# the incompatibility issue with Android Lint. When the issue is addressed, this target and the +# flag will be removed. +genrule( + name = "desugar_testdata_by_disabling_lambda_desugaring", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_without_lambda_desugared.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--min_sdk_version 17 -i $(location :testdata) -o $@ " + + "--nodesugar_try_with_resources_if_needed " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar) " + + "--only_desugar_javac9_for_lint", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +java_test( + name = "DesugarTryWithResourcesFunctionalTestWithNullStrategy", + size = "small", + srcs = [ + "DesugarTryWithResourcesFunctionalTest.java", + ], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=18", + "-Dcom.google.devtools.build.android.desugar.runtime.twr_disable_mimic=true", + "'-Dexpected.strategy=com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$NullDesugaringStrategy'", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarTryWithResourcesFunctionalTest", + deps = [ + "mocked_android_os_sdk_for_testing", + ":desugar_testdata_by_desugaring_try_with_resources", + "//src/test/java/com/google/devtools/build/android/desugar/runtime:throwable_extension_test_utility", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarTryWithResourcesFunctionalTestWithNullStrategyTwice", + size = "small", + srcs = [ + "DesugarTryWithResourcesFunctionalTest.java", + ], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=18", + "-Dcom.google.devtools.build.android.desugar.runtime.twr_disable_mimic=true", + "'-Dexpected.strategy=com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$NullDesugaringStrategy'", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarTryWithResourcesFunctionalTest", + deps = [ + "mocked_android_os_sdk_for_testing", + ":desugar_testdata_by_desugaring_try_with_resources_twice", # the lib desugared twice + "//src/test/java/com/google/devtools/build/android/desugar/runtime:throwable_extension_test_utility", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarTryWithResourcesFunctionalTestWithMimicStrategy", + size = "small", + srcs = [ + "DesugarTryWithResourcesFunctionalTest.java", + ], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=18", + "'-Dexpected.strategy=com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$MimicDesugaringStrategy'", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarTryWithResourcesFunctionalTest", + deps = [ + ":desugar_testdata_by_desugaring_try_with_resources", + ":mocked_android_os_sdk_for_testing", + "//src/test/java/com/google/devtools/build/android/desugar/runtime:throwable_extension_test_utility", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarTryWithResourcesFunctionalTestWithMimicStrategyTwice", + size = "small", + srcs = [ + "DesugarTryWithResourcesFunctionalTest.java", + ], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=18", + "'-Dexpected.strategy=com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$MimicDesugaringStrategy'", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarTryWithResourcesFunctionalTest", + deps = [ + ":desugar_testdata_by_desugaring_try_with_resources_twice", # the lib desugared twice. + ":mocked_android_os_sdk_for_testing", + "//src/test/java/com/google/devtools/build/android/desugar/runtime:throwable_extension_test_utility", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarTryWithResourcesFunctionalTestWithReuseStrategy", + size = "small", + srcs = [ + "DesugarTryWithResourcesFunctionalTest.java", + ], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=19", + "'-Dexpected.strategy=com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$ReuseDesugaringStrategy'", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarTryWithResourcesFunctionalTest", + deps = [ + ":desugar_testdata_by_desugaring_try_with_resources", + ":mocked_android_os_sdk_for_testing", + "//src/test/java/com/google/devtools/build/android/desugar/runtime:throwable_extension_test_utility", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DesugarTryWithResourcesFunctionalTestWithReuseStrategyTwice", + size = "small", + srcs = [ + "DesugarTryWithResourcesFunctionalTest.java", + ], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=19", + "'-Dexpected.strategy=com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$ReuseDesugaringStrategy'", + ], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.DesugarTryWithResourcesFunctionalTest", + deps = [ + ":desugar_testdata_by_desugaring_try_with_resources_twice", # the lib desugared twice. + ":mocked_android_os_sdk_for_testing", + "//src/test/java/com/google/devtools/build/android/desugar/runtime:throwable_extension_test_utility", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_library( + name = "mocked_android_os_sdk_for_testing", + srcs = ["mocked_android_framework/android/os/Build.java"], +) + +genrule( + name = "desugar_testdata_java8", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":separate_java8", + ":testdata_java8", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_java8.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--min_sdk_version 24 " + + "-i $(location :testdata_java8) -o $@ " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location :separate_java8) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_java8_like_in_android_studio", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":separate_java8", + ":testdata_java8_like_in_android_studio", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_java8_like_in_android_studio.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--copy_bridges_from_classpath " + + "--legacy_jacoco_fix " + + "--min_sdk_version 24 " + + "-i $(location :testdata_java8_like_in_android_studio) -o $@ " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location :separate_java8) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_with_default_methods", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":separate_java8", + ":testdata_java8", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_default_methods.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "-i $(location :testdata_java8) -o $@ " + + "--emit_dependency_metadata_as_needed " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location :separate_java8) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_separate_java8_with_default_methods", + srcs = [ + ":separate_java8", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["separate_java8_desugared_default_methods.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "-i $(location :separate_java8) -o $@ " + + "--emit_dependency_metadata_as_needed " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_with_default_methods_for_static_initializer_test", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":separate_java8", + ":testdata_java8", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_default_methods.output.txt"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--desugar_interface_method_bodies_if_needed -i $(location :testdata_java8) " + + "-o desugar_testdata_with_default_methods_for_static_initializer_test.jar " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location :separate_java8) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar) " + + " &> $@", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_with_default_methods_again", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":separate_java8", + ":testdata_desugared_default_methods.jar", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_default_methods_twice.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--desugar_interface_method_bodies_if_needed -i $(location :testdata_desugared_default_methods.jar) -o $@ " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location :separate_java8) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_core_library", + srcs = [ + ":testdata_core_library", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_core_library.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--core_library -i $(location :testdata_core_library) -o $@ " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_like_in_android_studio", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata_like_in_android_studio", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_like_in_android_studio.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--min_sdk_version 23 " + + "--legacy_jacoco_fix " + + "--nodesugar_interface_method_bodies_if_needed " + + "-i $(location :testdata_like_in_android_studio) -o $@ " + + "--copy_bridges_from_classpath " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_and_separate_with_multiple_inputs", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata_like_in_android_studio", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = [ + "testdata_desugared_with_multiple_inputs.jar", + "separate_desugared_with_multiple_inputs.jar", + ], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--min_sdk_version 23 --nodesugar_interface_method_bodies_if_needed " + + "-i $(location :testdata_like_in_android_studio) -o $(location testdata_desugared_with_multiple_inputs.jar) " + + "-i $(location :separate) -o $(location separate_desugared_with_multiple_inputs.jar) " + + "--copy_bridges_from_classpath " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_from_directory_to_jar", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata_like_in_android_studio", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = [ + "testdata_desugared_from_directory_to_jar.jar", + ], + cmd = """ + tmpdir=$$(mktemp -d) + # unzip input jar in order to pass a folder to desugar + pwddir=$$PWD + (cd $$tmpdir; $$pwddir/$(location //tools/zip:zipper) x $$pwddir/$(location :testdata_like_in_android_studio)) + $(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) \ + --min_sdk_version 23 --nodesugar_interface_method_bodies_if_needed \ + -i $$tmpdir -o $(location testdata_desugared_from_directory_to_jar.jar) \ + --copy_bridges_from_classpath \ + --classpath_entry $(location :separate) \ + --classpath_entry $(location //third_party:guava-jars) \ + --classpath_entry $(location //third_party/java/jacoco:blaze-agent) \ + --bootclasspath_entry $(location @bazel_tools//tools/android:android_jar) + rm -rf $$tmpdir + """, + tags = ["no_windows"], + tools = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar", + "//tools/zip:zipper", + ], +) + +genrule( + name = "desugar_testdata_from_directory_to_directory", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata_like_in_android_studio", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = [ + "testdata_desugared_from_directory_to_directory.jar", + ], + cmd = """ + tmpdirIn=$$(mktemp -d) + tmpdirOut=$$(mktemp -d) + # unzip input jar in order to pass a folder to desugar + pwddir=$$PWD + (cd $$tmpdirIn; $$pwddir/$(location //tools/zip:zipper) x $$pwddir/$(location :testdata_like_in_android_studio)) + $(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) \ + --min_sdk_version 23 --nodesugar_interface_method_bodies_if_needed \ + -i $$tmpdirIn -o $$tmpdirOut \ + --copy_bridges_from_classpath \ + --classpath_entry $(location :separate) \ + --classpath_entry $(location //third_party:guava-jars) \ + --classpath_entry $(location //third_party/java/jacoco:blaze-agent) \ + --bootclasspath_entry $(location @bazel_tools//tools/android:android_jar) + pushd $$tmpdirOut + $$pwddir/$(location //tools/zip:zipper) c $$pwddir/$(location testdata_desugared_from_directory_to_directory.jar) $$(find *) + popd + rm -rf $$tmpdirIn + rm -rf $$tmpdirOut + """, + tags = ["no_windows"], + tools = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar", + "//tools/zip:zipper", + ], +) + +genrule( + name = "desugar_testdata_with_classpath_directory", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata_like_in_android_studio", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = [ + "testdata_desugared_with_classpath_directory.jar", + ], + cmd = """ + tmpdir=$$(mktemp -d) + # unzip a classpath entry jar in order to pass a folder to desugar + pwddir=$$PWD + (cd $$tmpdir; $$pwddir/$(location //tools/zip:zipper) x $$pwddir/$(location :separate)) + $(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) \ + --min_sdk_version 23 --nodesugar_interface_method_bodies_if_needed \ + -i $(location :testdata_like_in_android_studio) -o $(location testdata_desugared_with_classpath_directory.jar) \ + --copy_bridges_from_classpath \ + --classpath_entry $$tmpdir \ + --classpath_entry $(location //third_party:guava-jars) \ + --classpath_entry $(location //third_party/java/jacoco:blaze-agent) \ + --bootclasspath_entry $(location @bazel_tools//tools/android:android_jar) + rm -rf $$tmpdir + """, + tags = ["no_windows"], + tools = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar", + "//tools/zip:zipper", + ], +) + +# This target tests the flag -desugar_try_with_resources_if_needed, for desugaring +# try-with-resources statements. +genrule( + name = "desugar_testdata_by_desugaring_try_with_resources", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_for_try_with_resources.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--nodesugar_interface_method_bodies_if_needed -i $(location :testdata) -o $@ " + + "--min_sdk_version 17 --desugar_try_with_resources_if_needed " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +# Desugars an interface with a default and a static interface method that was +# processed by a legacy version of JaCoCo that doesn't emit interface markers +# when calling methods it generated in interfaces (b/62623509). +genrule( + name = "desugar_default_method_with_legacy_coverage", + srcs = [ + "jacoco_0_7_5_default_method.jar", + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["jacoco_0_7_5_default_method_desugared.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--copy_bridges_from_classpath " + + "--legacy_jacoco_fix " + + "--min_sdk_version 19 " + + "-i $(location :jacoco_0_7_5_default_method.jar) -o $@ " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +# Disable desugaring try-with-resources by specifying a min_sdk_version above 18. +genrule( + name = "desugar_testdata_by_disabling_desugaring_try_with_resources_with_minsdkversion_above_18", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":testdata", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_for_NO_desugaring_try_with_resources.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--nodesugar_interface_method_bodies_if_needed -i $(location :testdata) -o $@ " + + "--min_sdk_version 19 --desugar_try_with_resources_if_needed " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_testdata_by_desugaring_try_with_resources_twice", + srcs = [ + "//third_party:guava-jars", + ":separate", + ":desugar_testdata_by_desugaring_try_with_resources", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_for_try_with_resources_twice.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "--nodesugar_interface_method_bodies_if_needed -i $(location :desugar_testdata_by_desugaring_try_with_resources) -o $@ " + + "--min_sdk_version 17 --desugar_try_with_resources_if_needed " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +# This target allows using the desugared jar as a regular dependency of the test +java_import( + name = "testdata_desugared", + jars = ["testdata_desugared.jar"], + deps = [":separate"], +) + +java_import( + name = "testdata_desugared_twice", + jars = ["testdata_desugared_twice.jar"], + deps = [":separate"], +) + +java_import( + name = "testdata_desugared_java8", + jars = ["testdata_desugared_java8.jar"], + deps = [ + ":separate", + ":separate_java8", # use un-desugared since we're not desugaring default methods here + ], +) + +java_import( + name = "testdata_desugared_java8_like_in_android_studio", + jars = ["testdata_desugared_java8_like_in_android_studio.jar"], + deps = [ + ":separate", + ":separate_java8", # use un-desugared since we're not desugaring default methods here + ], +) + +java_import( + name = "testdata_desugared_default_methods", + jars = ["testdata_desugared_default_methods.jar"], + deps = [ + ":separate", + ":separate_java8_desugared_default_methods", + ], +) + +java_import( + name = "testdata_desugared_default_methods_twice", + jars = ["testdata_desugared_default_methods_twice.jar"], + deps = [ + ":separate", + ":separate_java8_desugared_default_methods", + ], +) + +java_import( + name = "separate_java8_desugared_default_methods", + jars = ["separate_java8_desugared_default_methods.jar"], +) + +java_import( + name = "testdata_desugared_core_library", + jars = ["testdata_desugared_core_library.jar"], +) + +java_import( + name = "separate_desugared_with_multiple_inputs", + jars = ["separate_desugared_with_multiple_inputs.jar"], +) + +java_import( + name = "testdata_desugared_with_multiple_inputs", + jars = ["testdata_desugared_with_multiple_inputs.jar"], + deps = [":separate_desugared_with_multiple_inputs"], +) + +java_import( + name = "testdata_desugared_from_directory_to_jar", + jars = ["testdata_desugared_from_directory_to_jar.jar"], + deps = [":separate"], +) + +java_import( + name = "testdata_desugared_from_directory_to_directory", + jars = ["testdata_desugared_from_directory_to_directory.jar"], + deps = [":separate"], +) + +java_import( + name = "testdata_desugared_with_classpath_directory", + jars = ["testdata_desugared_with_classpath_directory.jar"], + deps = [":separate"], +) + +java_import( + name = "testdata_desugared_like_in_android_studio", + jars = ["testdata_desugared_like_in_android_studio.jar"], + deps = [":separate"], +) + +sh_test( + name = "stateless_lambda_has_no_factory_method_test", + srcs = ["diff.sh"], + args = [ + "$(location stateless_lambda_disassembled_golden.txt)", + "$(location stateless_lambda_disassembled.txt)", + ], + data = [ + # Golden file should show a $instance field and *no* get$Lambda() method + "stateless_lambda_disassembled_golden.txt", + ":stateless_lambda_disassembled.txt", + ], + tags = ["no_windows"], +) + +genrule( + name = "dump_stateless_lambda", + srcs = [":testdata_desugared.jar"], + outs = ["stateless_lambda_disassembled.txt"], + cmd = """ +$(JAVABASE)/bin/javap -c -p -cp $< 'com.google.devtools.build.android.desugar.testdata.Lambda$$$$Lambda$$0' > $@ +""", + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_host_java_runtime"], + tools = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +sh_test( + name = "capture_lambda_has_no_factory_method_test", + srcs = ["diff.sh"], + args = [ + "$(location capture_lambda_disassembled_golden.txt)", + "$(location capture_lambda_disassembled.txt)", + ], + data = [ + # Golden file should show a package-private constructor and *no* get$Lambda() method + "capture_lambda_disassembled_golden.txt", + ":capture_lambda_disassembled.txt", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +genrule( + name = "dump_capture_lambda", + srcs = [":testdata_desugared.jar"], + outs = ["capture_lambda_disassembled.txt"], + cmd = """ +$(JAVABASE)/bin/javap -c -p -cp $< 'com.google.devtools.build.android.desugar.testdata.CaptureLambda$$$$Lambda$$0' > $@ +""", + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], + tools = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +sh_test( + name = "simple_method_reference_has_no_factory_method_test", + srcs = ["diff.sh"], + args = [ + "$(location simple_instance_method_reference_disassembled_golden.txt)", + "$(location simple_instance_method_reference_disassembled.txt)", + ], + data = [ + # Golden file should show a package-private constructor and *no* get$Lambda() method + "simple_instance_method_reference_disassembled_golden.txt", + ":simple_instance_method_reference_disassembled.txt", + ], + tags = ["no_windows"], +) + +genrule( + name = "dump_simple_instance_method_reference", + srcs = [":testdata_desugared.jar"], + outs = ["simple_instance_method_reference_disassembled.txt"], + cmd = """ +$(JAVABASE)/bin/javap -c -p -cp $< 'com.google.devtools.build.android.desugar.testdata.MethodReferenceSuperclass$$$$Lambda$$0' > $@ +""", + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], + tools = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +sh_test( + name = "interface_has_method_bodies_removed", + srcs = ["diff.sh"], + args = [ + "$(location interface_with_desugared_method_bodies_disassembled_golden.txt)", + "$(location interface_with_desugared_method_bodies_disassembled.txt)", + ], + data = [ + # Golden file should show abstract methods only and *no* static methods + "interface_with_desugared_method_bodies_disassembled_golden.txt", + ":interface_with_desugared_method_bodies_disassembled.txt", + ], + tags = ["no_windows"], +) + +genrule( + name = "dump_interface_with_method_bodies", + srcs = [":testdata_desugared_default_methods.jar"], + outs = ["interface_with_desugared_method_bodies_disassembled.txt"], + # drop jacoco fields and static initializers, which jacoco may add, so the output is stable + # with or without coverage instrumentation + cmd = """ +$(JAVABASE)/bin/javap -p -cp $< 'com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod' | grep -v 'jacoco' | grep -v 'static {}' > $@ +""", + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], + tools = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +sh_test( + name = "inherited_abstract_method_gets_no_default_method_stub", + srcs = ["diff.sh"], + args = [ + "$(location class_with_inherited_abstract_method_disassembled_golden.txt)", + "$(location class_with_inherited_abstract_method_disassembled.txt)", + ], + data = [ + # Golden file should show *no* methods + "class_with_inherited_abstract_method_disassembled_golden.txt", + ":class_with_inherited_abstract_method_disassembled.txt", + ], + tags = ["no_windows"], +) + +genrule( + name = "dump_class_with_inherited_abstract_method", + srcs = [":testdata_desugared_default_methods.jar"], + outs = ["class_with_inherited_abstract_method_disassembled.txt"], + # drop jacoco fields and static initializers, which jacoco may add, so the output is stable + # with or without coverage instrumentation + cmd = """ +$(JAVABASE)/bin/javap -p -cp $< 'com.google.devtools.build.android.desugar.testdata.java8.Named$$AbstractName' | grep -v 'jacoco' | grep -v 'static {}' > $@ +""", + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], + tools = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +sh_test( + name = "no_stubs_for_lambdas_in_implemented_interface", + srcs = ["diff.sh"], + args = [ + "$(location class_with_lambdas_in_implemented_interface_disassembled_golden.txt)", + "$(location class_with_lambdas_in_implemented_interface_disassembled.txt)", + ], + data = [ + # Golden file should show *no* lambda$xxx methods (and 4 stubs for default methods) + "class_with_lambdas_in_implemented_interface_disassembled_golden.txt", + ":class_with_lambdas_in_implemented_interface_disassembled.txt", + ], + tags = ["no_windows"], +) + +genrule( + name = "dump_class_with_lambdas_in_implemented_interface", + srcs = [":testdata_desugared_default_methods.jar"], + outs = ["class_with_lambdas_in_implemented_interface_disassembled.txt"], + # drop jacoco fields and static initializers, which jacoco may add, so the output is stable + # with or without coverage instrumentation + cmd = """ +$(JAVABASE)/bin/javap -p -cp $< 'com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod$$Concrete' | grep -v 'jacoco' | grep -v 'static {}' > $@ +""", + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], + tools = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +sh_test( + name = "desugar_idempotency_test", + srcs = ["diff.sh"], + args = [ + "$(location :capture_lambda_desugared.jar)", + "$(location :capture_lambda_desugared_twice.jar)", + ], + data = [ + ":capture_lambda_desugared.jar", + ":capture_lambda_desugared_twice.jar", + ], + tags = ["no_windows"], +) + +# Smaller Jar used for idempotency test. CaptureLambda can be desugared without +# any need to fix up the resulting lambda class. Running the desugaring tool +# over lambda classes that required fixes somehow perturbs the constant pool +# (but nothing else) so for simplicity we use this Jar for the idempotency test +# we don't run into the perturbance issue. +java_library( + name = "capture_lambda", + srcs = ["testdata/CaptureLambda.java"], +) + +genrule( + name = "desugar_capture_lambda", + srcs = [ + ":capture_lambda", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["capture_lambda_desugared.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "-i $(location :capture_lambda) -o $@ " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_capture_lambda_again", + srcs = [ + ":capture_lambda_desugared.jar", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["capture_lambda_desugared_twice.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "-i $(location :capture_lambda_desugared.jar) -o $@ " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +sh_test( + name = "testdata_sanity_test", + srcs = ["diff.sh"], + args = [ + "$(location :baseclass_lambda_signature.txt)", + "$(location :subclass_lambda_signature.txt)", + ], + data = [ + ":baseclass_lambda_signature.txt", + ":subclass_lambda_signature.txt", + ], + tags = ["no_windows"], +) + +genrule( + name = "baseclass_lambda_signature", + srcs = [":testdata"], + outs = [ + "baseclass_disassembled.txt", + "baseclass_lambda_signature.txt", + ], + cmd = """ +$(JAVABASE)/bin/javap -p -cp $< com.google.devtools.build.android.desugar.testdata.OuterReferenceLambda > $(location baseclass_disassembled.txt) +grep lambda $(location baseclass_disassembled.txt) > $(location baseclass_lambda_signature.txt)""", + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], + tools = ["@bazel_tools//tools/jdk:current_java_runtime"], +) + +genrule( + name = "subclass_lambda_signature", + srcs = [":testdata"], + outs = [ + "subclass_disassembled.txt", + "subclass_lambda_signature.txt", + ], + cmd = """ +$(JAVABASE)/bin/javap -p -cp $< com.google.devtools.build.android.desugar.testdata.LambdaInOverride > $(location subclass_disassembled.txt) +grep lambda $(location subclass_disassembled.txt) > $(location subclass_lambda_signature.txt)""", + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], + tools = [ + "@bazel_tools//tools/jdk:current_java_runtime", + ], +) + +_SINGLEJAR_IMPLS = { + "singlejar": "//src/tools/singlejar:singlejar_local", +} + +[sh_test( + name = "desugar_deps_consistent_%s_test" % label, + srcs = ["desugar_deps_consistency_test.sh"], + args = [ + "$(location %s)" % singlejar, + "$(JAVABASE)/bin/jar", + "$(location :testdata_desugared_default_methods.jar)", + "$(location :separate_java8_desugared_default_methods.jar)", + "$(location :guava_at_head_desugared.jar)", + ], + data = [ + ":guava_at_head_desugared.jar", + ":separate_java8_desugared_default_methods.jar", + ":testdata_desugared_default_methods.jar", + singlejar, + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) for label, singlejar in _SINGLEJAR_IMPLS.items()] + +# Tests that deps checking fails due to missing default method: libseparate_java8.jar defines +# default methods which testdata_desugared_default_methods.jar assumes have been moved to companion. +[sh_test( + name = "desugar_deps_missing_%s_fail_test" % label, + srcs = ["desugar_deps_consistency_test.sh"], + args = [ + "$(location %s)" % singlejar, + "$(JAVABASE)/bin/jar", + "$(location :testdata_desugared_default_methods.jar)", + "$(location :libseparate_java8.jar)", + "$(location :guava_at_head_desugared.jar)", + ], + data = [ + ":guava_at_head_desugared.jar", + ":libseparate_java8.jar", + ":testdata_desugared_default_methods.jar", + singlejar, + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) for label, singlejar in _SINGLEJAR_IMPLS.items()] + +# Regression test for b/68049457 with deps checking failing due to missing static method: +# b68049457_caller_desugared.jar assumes presence of companion class that's missing (in fact, +# the whole Jar is missing, similar to neverlink situations). +[sh_test( + name = "desugar_deps_b68049457_%s_fail_test" % label, + srcs = ["desugar_deps_consistency_test.sh"], + args = [ + "$(location %s)" % singlejar, + "$(JAVABASE)/bin/jar", + "$(location :b68049457_caller_desugared.jar)", + ], + data = [ + ":b68049457_caller_desugared.jar", + singlejar, + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) for label, singlejar in _SINGLEJAR_IMPLS.items()] + +[sh_test( + name = "desugar_deps_missed_default_method_%s_fail_test" % label, + srcs = ["desugar_deps_consistency_test.sh"], + args = [ + "$(location %s)" % singlejar, + "$(JAVABASE)/bin/jar", + "$(location :testdata_desugared_with_missing_dep.jar)", + "$(location :separate_java8_desugared_default_methods.jar)", + "$(location :guava_at_head_desugared.jar)", + ], + data = [ + ":guava_at_head_desugared.jar", + ":separate_java8_desugared_default_methods.jar", + ":testdata_desugared_with_missing_dep.jar", + singlejar, + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) for label, singlejar in _SINGLEJAR_IMPLS.items()] + +# Tests that any Jar entry starting with j$/ causes singlejar to fail when run +# with --check_desugar_deps. This is a backstop against such entries being +# included in deploy.jars. +[sh_test( + name = "bad_entry_jar_%s_fail_test" % label, + srcs = ["desugar_deps_consistency_test.sh"], + args = [ + "$(location %s)" % singlejar, + "$(JAVABASE)/bin/jar", + "$(location :mock_bad_entry_jar)", + ], + data = [ + ":mock_bad_entry_jar", + singlejar, + "@bazel_tools//tools/jdk:current_java_runtime", + ], + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], +) for label, singlejar in _SINGLEJAR_IMPLS.items()] + +genrule( + name = "desugar_testdata_with_missing_dep", + srcs = [ + ":separate", + ":testdata_java8", + "//third_party:guava-jars", + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["testdata_desugared_with_missing_dep.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "-i $(location :testdata_java8) -o $@ " + + "--emit_dependency_metadata_as_needed " + + "--classpath_entry $(location :separate) " + + "--classpath_entry $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party/java/jacoco:blaze-agent) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +genrule( + name = "desugar_b68049457_caller", + srcs = [ + ":b68049457_caller", + ":b68049457_interface", + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["b68049457_caller_desugared.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "-i $(location :b68049457_caller) -o $@ " + + "--emit_dependency_metadata_as_needed " + + "--classpath_entry $(location :b68049457_interface) " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +java_library( + name = "b68049457_interface", + srcs = ["testdata/b68049457/StaticInterfaceMethod.java"], + neverlink = 1, +) + +java_library( + name = "b68049457_caller", + srcs = ["testdata/b68049457/StaticInterfaceMethodCaller.java"], + deps = [":b68049457_interface"], +) + +# Creates simple .jar file with empty file entry called "j$/hello" +genrule( + name = "mock_bad_entry_jar", + outs = ["mock_bad_entry.jar"], + cmd = """$(location //tools/zip:zipper) c $@ "j\\$$/hello=" """, + tools = ["//tools/zip:zipper"], +) + +# b/62623509 regression test. Calls to jacoco-generated method in interface +# need to be rewritten to call the companion class even though the original +# bytecode falsely doesn't indicate that the method is declared in an +# interface. Jacoco-generated field is *not* moved. +sh_test( + name = "jacoco_legacy_default_method_regression_test", + srcs = ["diff.sh"], + args = [ + "$(location :jacoco_legacy_default_method_companion_disassembled_golden.txt)", + "$(location :jacoco_legacy_default_method_companion_disassembled.txt)", + ], + data = [ + "jacoco_legacy_default_method_companion_disassembled_golden.txt", + ":jacoco_legacy_default_method_companion_disassembled.txt", + ], + tags = ["no_windows"], +) + +genrule( + name = "dump_jacoco_legacy_default_method_companion", + srcs = [":jacoco_0_7_5_default_method_desugared.jar"], + outs = ["jacoco_legacy_default_method_companion_disassembled.txt"], + cmd = """$(JAVABASE)/bin/javap -c -p -cp $< 'com/example/gavra/java8coverage/Defaults$$$$CC' > $@""", + tags = ["no_windows"], + toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"], + tools = [ + "@bazel_tools//tools/jdk:current_java_runtime", + ], +) + +# Regression test for b/70415451 +genrule( + name = "desugar_guava_at_head", + srcs = [ + "//third_party:guava-jars", + "//third_party:guava-failureaccess-jar", + # Depend on Jacoco runtime in case testdata was built with coverage + # instrumentation + "//third_party/java/jacoco:blaze-agent", + "@bazel_tools//tools/android:android_jar", + ], + outs = ["guava_at_head_desugared.jar"], + cmd = "$(location //src/tools/android/java/com/google/devtools/build/android/desugar:Desugar) " + + "-i $(location //third_party:guava-jars) " + + "--classpath_entry $(location //third_party:guava-failureaccess-jar) " + + "-o $@ " + + "--bootclasspath_entry $(location @bazel_tools//tools/android:android_jar)", + tags = ["no_windows"], + tools = ["//src/tools/android/java/com/google/devtools/build/android/desugar:Desugar"], +) + +java_binary( + name = "generate_lambda_with_constant_arguments", + srcs = ["Bug62060793TestDataGenerator.java"], + main_class = "com.google.devtools.build.android.desugar.Bug62060793TestDataGenerator", + deps = [ + "//third_party:asm", + "//third_party:guava", + ], +) + +java_binary( + name = "generate_synthetic_method_with_lambda_name_convention", + srcs = ["Bug62456849TestDataGenerator.java"], + main_class = "com.google.devtools.build.android.desugar.Bug62456849TestDataGenerator", + deps = [ + "//third_party:asm", + "//third_party:guava", + ], +) + +java_test( + name = "ClassSignatureParserTest", + size = "small", + srcs = ["ClassSignatureParserTest.java"], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.ClassSignatureParserTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "CoreLibrarySupportTest", + size = "small", + srcs = ["CoreLibrarySupportTest.java"], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.CoreLibrarySupportTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel:desugar_class_attr_java_proto", + "//src/tools/android/java/com/google/devtools/build/android/desugar/retarget", + "//src/tools/android/java/com/google/devtools/build/android/desugar/retarget:retarget_java_proto", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "CorePackageRenamerTest", + size = "small", + srcs = ["CorePackageRenamerTest.java"], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.CorePackageRenamerTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "FrameInfoTest", + size = "small", + srcs = ["FrameInfoTest.java"], + tags = ["no_windows"], + test_class = "com.google.devtools.build.android.desugar.FrameInfoTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/Bug62060793TestDataGenerator.java b/src/test/java/com/google/devtools/build/android/desugar/Bug62060793TestDataGenerator.java new file mode 100644 index 00000000000000..1b88693dd5dae7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/Bug62060793TestDataGenerator.java @@ -0,0 +1,372 @@ +// 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 ", 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, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()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", "", "(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/src/test/java/com/google/devtools/build/android/desugar/Bug62456849TestDataGenerator.java b/src/test/java/com/google/devtools/build/android/desugar/Bug62456849TestDataGenerator.java new file mode 100644 index 00000000000000..ec10b61e31cd72 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/Bug62456849TestDataGenerator.java @@ -0,0 +1,105 @@ +// 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 com.google.common.collect.Iterators; +import com.google.common.collect.UnmodifiableIterator; +import com.google.common.io.ByteStreams; +import org.objectweb.asm.Opcodes; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * Test data generator for b/62456849. This class converts methods satisfying the following + * conditions to synthetic methods. + *
  • The name starts with "lambda$" + *
  • Not synthetic + */ +public class Bug62456849TestDataGenerator { + + public static void main(String[] args) throws IOException { + checkArgument( + args.length == 2, + "Usage: %s ", + Bug62456849TestDataGenerator.class.getName()); + Path inputJar = Paths.get(args[0]); + checkArgument(Files.isRegularFile(inputJar), "The input jar %s is not a file", inputJar); + Path outputJar = Paths.get(args[1]); + + try (ZipFile inputZip = new ZipFile(inputJar.toFile()); + ZipOutputStream outZip = + new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(outputJar)))) { + for (UnmodifiableIterator it = + Iterators.forEnumeration(inputZip.entries()); + it.hasNext(); ) { + ZipEntry entry = it.next(); + String entryName = entry.getName(); + byte[] content = + entryName.endsWith(".class") + ? convertClass(inputZip, entry) + : readEntry(inputZip, entry); + writeToZipFile(outZip, entryName, content); + } + } + } + + 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[] readEntry(ZipFile file, ZipEntry entry) throws IOException { + try (InputStream is = file.getInputStream(entry)) { + return ByteStreams.toByteArray(is); + } + } + + private static byte[] convertClass(ZipFile file, ZipEntry entry) throws IOException { + try (InputStream content = file.getInputStream(entry)) { + ClassReader reader = new ClassReader(content); + ClassWriter writer = new ClassWriter(0); + ClassVisitor converter = + new ClassVisitor(Opcodes.ASM9, writer) { + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (name.startsWith("lambda$") && (access & Opcodes.ACC_SYNTHETIC) == 0) { + access |= Opcodes.ACC_SYNTHETIC; + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + }; + reader.accept(converter, 0); + return writer.toByteArray(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java b/src/test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java new file mode 100644 index 00000000000000..94973551e9bd1e --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/ByteCodeTypePrinter.java @@ -0,0 +1,242 @@ +// 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 org.objectweb.asm.Opcodes; +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.Comparator; +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.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 getSortedClassEntriess(ZipFile jar) { + return jar.stream() + .filter(entry -> entry.getName().endsWith(".class")) + .sorted(Comparator.comparing(ZipEntry::getName)) + .collect(ImmutableList.toImmutableList()); + } + + private static class ClassWithTypeDumper extends ClassVisitor { + + private String internalName; + private final PrintWriter printWriter; + + public ClassWithTypeDumper(PrintWriter printWriter) { + super(Opcodes.ASM9); + 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.ASM9); + } + + 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.ASM9, visitor); + this.inference = inference; + this.printWriter = printWriter; + } + + private void printTypeOfOperandStack() { + printer.print(" |__STACK: " + inference.getOperandStackAsString() + "\n"); + printer.print(" |__LOCAL: " + inference.getLocalsAsString() + "\n"); + } + + @Override + public void visitIntInsn(int opcode, int operand) { + printer.visitIntInsn(opcode, operand); + printTypeOfOperandStack(); + super.visitIntInsn(opcode, operand); + } + + @Override + public void visitInsn(int opcode) { + printer.visitInsn(opcode); + printTypeOfOperandStack(); + super.visitInsn(opcode); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + printer.visitMultiANewArrayInsn(desc, dims); + printTypeOfOperandStack(); + super.visitMultiANewArrayInsn(desc, dims); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + printer.visitLookupSwitchInsn(dflt, keys, labels); + printTypeOfOperandStack(); + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + printer.visitTableSwitchInsn(min, max, dflt, labels); + printTypeOfOperandStack(); + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitIincInsn(int var, int increment) { + printer.visitIincInsn(var, increment); + printTypeOfOperandStack(); + super.visitIincInsn(var, increment); + } + + @Override + public void visitLdcInsn(Object cst) { + printer.visitLdcInsn(cst); + printTypeOfOperandStack(); + super.visitLdcInsn(cst); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + printer.visitJumpInsn(opcode, label); + printTypeOfOperandStack(); + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + printer.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + printTypeOfOperandStack(); + 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); + printTypeOfOperandStack(); + 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); + printTypeOfOperandStack(); + super.visitMethodInsn(opcode, owner, name, desc); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + printer.visitFieldInsn(opcode, owner, name, desc); + printTypeOfOperandStack(); + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + printer.visitTypeInsn(opcode, type); + printTypeOfOperandStack(); + super.visitTypeInsn(opcode, type); + } + + @Override + public void visitVarInsn(int opcode, int var) { + printer.visitVarInsn(opcode, var); + printTypeOfOperandStack(); + super.visitVarInsn(opcode, var); + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + printer.visitFrame(type, nLocal, local, nStack, stack); + super.visitFrame(type, nLocal, local, nStack, stack); + } + + @Override + public void visitLabel(Label label) { + printer.visitLabel(label); + super.visitLabel(label); + } + + @Override + public void visitEnd() { + printer.print(printWriter); + printWriter.flush(); + super.visitEnd(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt b/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt new file mode 100644 index 00000000000000..4d0e042b0817fa --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.golden.txt @@ -0,0 +1,1810 @@ +Class: ClassInDefaultPackageThatStartsWithC.class +Method + L0 + ALOAD 0 + |__STACK: [LClassInDefaultPackageThatStartsWithC;] + |__LOCAL: [LClassInDefaultPackageThatStartsWithC;] + INVOKESPECIAL java/lang/Object. ()V + |__STACK: [] + |__LOCAL: [LClassInDefaultPackageThatStartsWithC;] + RETURN + |__STACK: [] + |__LOCAL: [LClassInDefaultPackageThatStartsWithC;] + + + +Class: testsubjects/TestSubject.class +Method + L0 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + |__LOCAL: [Ltestsubjects/TestSubject;] + INVOKESPECIAL java/lang/Object. ()V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;] + RETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;] +Method catchTest + L0 + ALOAD 0 + |__STACK: [Ljava/lang/Object;] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + INSTANCEOF java/lang/String + |__STACK: [I] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + IFNE L1 + |__STACK: [] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + L2 + GETSTATIC testsubjects/TestSubject.VALUE_ONE : I + |__STACK: [I] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + IRETURN + |__STACK: [] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + L1 + FRAME SAME + ALOAD 0 + |__STACK: [Ljava/lang/Object;] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + CHECKCAST java/lang/String + |__STACK: [Ljava/lang/String;] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + INVOKESTATIC java/util/regex/Pattern.compile (Ljava/lang/String;)Ljava/util/regex/Pattern; + |__STACK: [Ljava/util/regex/Pattern;] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + POP + |__STACK: [] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + L3 + GOTO L4 + |__STACK: [] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + L5 + FRAME SAME1 java/util/regex/PatternSyntaxException + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;, Ljava/util/regex/PatternSyntaxException;] + L6 + GETSTATIC testsubjects/TestSubject.VALUE_TWO : I + |__STACK: [I] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;, Ljava/util/regex/PatternSyntaxException;] + IRETURN + |__STACK: [] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;, Ljava/util/regex/PatternSyntaxException;] + L4 + FRAME SAME + GETSTATIC testsubjects/TestSubject.VALUE_ONE : I + |__STACK: [I] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] + IRETURN + |__STACK: [] + |__LOCAL: [Ljava/lang/Object;, Ljava/lang/Object;] +Method assertEquals + L0 + DLOAD 1 + |__STACK: [D, TOP] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DLOAD 3 + |__STACK: [D, TOP, D, TOP] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKESTATIC java/lang/Double.compare (DD)I + |__STACK: [I] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + IFNE L1 + |__STACK: [] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + L2 + RETURN + |__STACK: [] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + L1 + FRAME SAME + DLOAD 1 + |__STACK: [D, TOP] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DLOAD 3 + |__STACK: [D, TOP, D, TOP] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DSUB + |__STACK: [D, TOP] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKESTATIC java/lang/Math.abs (D)D + |__STACK: [D, TOP] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DLOAD 5 + |__STACK: [D, TOP, D, TOP] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DCMPG + |__STACK: [I] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + IFLE L3 + |__STACK: [] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + L4 + NEW java/lang/RuntimeException + |__STACK: [Ljava/lang/RuntimeException;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DUP + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + NEW java/lang/StringBuilder + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DUP + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/StringBuilder;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKESPECIAL java/lang/StringBuilder. ()V + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + ALOAD 0 + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/String;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + NEW java/lang/Double + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DUP + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;, Ljava/lang/Double;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DLOAD 1 + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;, Ljava/lang/Double;, D, TOP] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKESPECIAL java/lang/Double. (D)V + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder; + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + NEW java/lang/Double + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DUP + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;, Ljava/lang/Double;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + DLOAD 3 + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;, Ljava/lang/Double;, D, TOP] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKESPECIAL java/lang/Double. (D)V + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;, Ljava/lang/Double;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder; + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/StringBuilder;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; + |__STACK: [Ljava/lang/RuntimeException;, Ljava/lang/RuntimeException;, Ljava/lang/String;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + INVOKESPECIAL java/lang/RuntimeException. (Ljava/lang/String;)V + |__STACK: [Ljava/lang/RuntimeException;] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + ATHROW + |__STACK: [] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] + L3 + FRAME SAME + RETURN + |__STACK: [] + |__LOCAL: [Ljava/lang/String;, D, TOP, D, TOP, D, TOP] +Method simpleTryWithResources + L0 + NEW testsubjects/TestSubject$SimpleResource + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [] + DUP + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;, Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [] + INVOKESPECIAL testsubjects/TestSubject$SimpleResource. ()V + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [] + ASTORE 0 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;] + ACONST_NULL + |__STACK: [NULL] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;] + ASTORE 1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + L1 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + ICONST_1 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;, I] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.call (Z)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + L2 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + IFNULL L3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + ALOAD 1 + |__STACK: [NULL] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + IFNULL L4 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + L5 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.close ()V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + L6 + GOTO L3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, NULL] + L7 + FRAME FULL [testsubjects/TestSubject$SimpleResource java/lang/Throwable] [java/lang/Throwable] + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 1 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + GOTO L3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L4 + FRAME SAME + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.close ()V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;] + GOTO L3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;] + L8 + FRAME SAME1 java/lang/Throwable + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ASTORE 1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L9 + FRAME SAME1 java/lang/Throwable + ASTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + L10 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + IFNULL L11 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + ALOAD 1 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + IFNULL L12 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + L13 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.close ()V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + L14 + GOTO L11 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + L15 + FRAME FULL [testsubjects/TestSubject$SimpleResource java/lang/Throwable T java/lang/Throwable] [java/lang/Throwable] + ASTORE 4 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 1 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 4 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + GOTO L11 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L12 + FRAME SAME + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject$SimpleResource;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + INVOKEVIRTUAL testsubjects/TestSubject$SimpleResource.close ()V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + L11 + FRAME SAME + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject$SimpleResource;, Ljava/lang/Throwable;, TOP, Ljava/lang/Throwable;] + L3 + FRAME FULL [] [] + RETURN + |__STACK: [] + |__LOCAL: [] +Method internalCompare + L0 + ALOAD 4 + |__STACK: [Ljava/util/function/BinaryOperator;] + |__LOCAL: [J, TOP, J, TOP, Ljava/util/function/BinaryOperator;] + LLOAD 0 + |__STACK: [Ljava/util/function/BinaryOperator;, J, TOP] + |__LOCAL: [J, TOP, J, TOP, Ljava/util/function/BinaryOperator;] + INVOKESTATIC java/lang/Long.valueOf (J)Ljava/lang/Long; + |__STACK: [Ljava/util/function/BinaryOperator;, Ljava/lang/Long;] + |__LOCAL: [J, TOP, J, TOP, Ljava/util/function/BinaryOperator;] + LLOAD 2 + |__STACK: [Ljava/util/function/BinaryOperator;, Ljava/lang/Long;, J, TOP] + |__LOCAL: [J, TOP, J, TOP, Ljava/util/function/BinaryOperator;] + INVOKESTATIC java/lang/Long.valueOf (J)Ljava/lang/Long; + |__STACK: [Ljava/util/function/BinaryOperator;, Ljava/lang/Long;, Ljava/lang/Long;] + |__LOCAL: [J, TOP, J, TOP, Ljava/util/function/BinaryOperator;] + INVOKEINTERFACE java/util/function/BinaryOperator.apply (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; (itf) + |__STACK: [Ljava/lang/Object;] + |__LOCAL: [J, TOP, J, TOP, Ljava/util/function/BinaryOperator;] + CHECKCAST java/lang/Long + |__STACK: [Ljava/lang/Long;] + |__LOCAL: [J, TOP, J, TOP, Ljava/util/function/BinaryOperator;] + INVOKEVIRTUAL java/lang/Long.longValue ()J + |__STACK: [J, TOP] + |__LOCAL: [J, TOP, J, TOP, Ljava/util/function/BinaryOperator;] + LRETURN + |__STACK: [] + |__LOCAL: [J, TOP, J, TOP, Ljava/util/function/BinaryOperator;] +Method closeResourceArray + L0 + ALOAD 1 + |__STACK: [[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;] + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;] + ALOAD 2 + |__STACK: [[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;] + ARRAYLENGTH + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;] + ISTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I] + ICONST_0 + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I] + ISTORE 4 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + L1 + FRAME APPEND [[Ljava/sql/Statement; I I] + ILOAD 4 + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + ILOAD 3 + |__STACK: [I, I] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + IF_ICMPGE L2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + ALOAD 2 + |__STACK: [[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + ILOAD 4 + |__STACK: [[Ljava/sql/Statement;, I] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + AALOAD + |__STACK: [Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + ASTORE 5 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + L3 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + ALOAD 5 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + ACONST_NULL + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + L4 + IINC 4 1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + GOTO L1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + L2 + FRAME CHOP 3 + RETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [Ljava/sql/Statement;] +Method closeResourceMultiArray + L0 + ALOAD 1 + |__STACK: [[[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;] + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;] + ALOAD 2 + |__STACK: [[[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;] + ARRAYLENGTH + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;] + ISTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I] + ICONST_0 + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I] + ISTORE 4 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I] + L1 + FRAME APPEND [[[Ljava/sql/Statement; I I] + ILOAD 4 + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I] + ILOAD 3 + |__STACK: [I, I] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I] + IF_ICMPGE L2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I] + ALOAD 2 + |__STACK: [[[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I] + ILOAD 4 + |__STACK: [[[Ljava/sql/Statement;, I] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I] + AALOAD + |__STACK: [[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I] + ASTORE 5 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;] + L3 + ALOAD 5 + |__STACK: [[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;] + ASTORE 6 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;] + ALOAD 6 + |__STACK: [[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;] + ARRAYLENGTH + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;] + ISTORE 7 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I] + ICONST_0 + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I] + ISTORE 8 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + 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] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + ILOAD 7 + |__STACK: [I, I] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + IF_ICMPGE L5 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + ALOAD 6 + |__STACK: [[Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + ILOAD 8 + |__STACK: [[Ljava/sql/Statement;, I] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + AALOAD + |__STACK: [Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I] + ASTORE 9 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + L6 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + ALOAD 9 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + ACONST_NULL + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + L7 + IINC 8 1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + GOTO L4 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I, [Ljava/sql/Statement;, [Ljava/sql/Statement;, I, I, Ljava/sql/Statement;] + L5 + FRAME FULL [testsubjects/TestSubject [[Ljava/sql/Statement; [[Ljava/sql/Statement; I I] [] + IINC 4 1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I] + GOTO L1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;, [[Ljava/sql/Statement;, I, I] + L2 + FRAME CHOP 3 + RETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, [[Ljava/sql/Statement;] +Method closeResourceArrayList + L0 + ALOAD 1 + |__STACK: [Ljava/util/List;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;] + INVOKEINTERFACE java/util/List.iterator ()Ljava/util/Iterator; (itf) + |__STACK: [Ljava/util/Iterator;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;] + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;] + L1 + FRAME APPEND [java/util/Iterator] + ALOAD 2 + |__STACK: [Ljava/util/Iterator;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;] + INVOKEINTERFACE java/util/Iterator.hasNext ()Z (itf) + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;] + IFEQ L2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;] + ALOAD 2 + |__STACK: [Ljava/util/Iterator;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;] + INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; (itf) + |__STACK: [Ljava/lang/Object;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;] + CHECKCAST java/sql/Statement + |__STACK: [Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;] + ASTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;, Ljava/sql/Statement;] + L3 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;, Ljava/sql/Statement;] + ALOAD 3 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;, Ljava/sql/Statement;] + ACONST_NULL + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;, Ljava/sql/Statement;] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;, Ljava/sql/Statement;] + L4 + GOTO L1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;, Ljava/util/Iterator;, Ljava/sql/Statement;] + L2 + FRAME CHOP 1 + RETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/util/List;] +Method closeSqlStmt + L0 + ACONST_NULL + |__STACK: [NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;] + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, NULL] + L1 + ALOAD 1 + |__STACK: [Ljava/sql/Connection;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, NULL] + INVOKEINTERFACE java/sql/Connection.createStatement ()Ljava/sql/Statement; (itf) + |__STACK: [Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, NULL] + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;] + L2 + GOTO L3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;] + L4 + FRAME FULL [testsubjects/TestSubject java/sql/Connection java/sql/Statement] [java/sql/SQLException] + ASTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;, Ljava/sql/SQLException;] + L5 + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;, Ljava/sql/SQLException;] + ALOAD 2 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;, Ljava/sql/SQLException;] + ALOAD 3 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, Ljava/sql/SQLException;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;, Ljava/sql/SQLException;] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;, Ljava/sql/SQLException;] + L3 + FRAME SAME + ALOAD 0 + |__STACK: [Ltestsubjects/TestSubject;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;] + ALOAD 2 + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;] + ACONST_NULL + |__STACK: [Ltestsubjects/TestSubject;, Ljava/sql/Statement;, NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;] + INVOKEVIRTUAL testsubjects/TestSubject.closeResource (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;] + L6 + RETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/sql/Connection;, Ljava/sql/Statement;] +Method closeResource + L0 + ALOAD 1 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + IFNONNULL L1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L2 + RETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L1 + FRAME SAME + ALOAD 1 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L3 + GOTO L4 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L5 + FRAME SAME1 java/lang/Exception + ASTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Exception;] + L6 + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Exception;] + IFNULL L7 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Exception;] + L8 + ALOAD 2 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Exception;] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Exception;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Exception;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Exception;] + L7 + FRAME APPEND [java/lang/Exception] + ALOAD 3 + |__STACK: [Ljava/lang/Exception;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Exception;] + ATHROW + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Exception;] + L4 + FRAME CHOP 1 + RETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] +Method intAdd + L0 + ILOAD 0 + |__STACK: [I] + |__LOCAL: [I, I] + ISTORE 2 + |__STACK: [] + |__LOCAL: [I, I, I] + L1 + IINC 2 1 + |__STACK: [] + |__LOCAL: [I, I, I] + L2 + IINC 2 1 + |__STACK: [] + |__LOCAL: [I, I, I] + L3 + ILOAD 2 + |__STACK: [I] + |__LOCAL: [I, I, I] + ILOAD 1 + |__STACK: [I, I] + |__LOCAL: [I, I, I] + IADD + |__STACK: [I] + |__LOCAL: [I, I, I] + ISTORE 2 + |__STACK: [] + |__LOCAL: [I, I, I] + L4 + IINC 2 -1 + |__STACK: [] + |__LOCAL: [I, I, I] + L5 + IINC 2 -1 + |__STACK: [] + |__LOCAL: [I, I, I] + L6 + ILOAD 2 + |__STACK: [I] + |__LOCAL: [I, I, I] + ILOAD 1 + |__STACK: [I, I] + |__LOCAL: [I, I, I] + ISUB + |__STACK: [I] + |__LOCAL: [I, I, I] + ISTORE 2 + |__STACK: [] + |__LOCAL: [I, I, I] + L7 + ILOAD 2 + |__STACK: [I] + |__LOCAL: [I, I, I] + ILOAD 1 + |__STACK: [I, I] + |__LOCAL: [I, I, I] + IMUL + |__STACK: [I] + |__LOCAL: [I, I, I] + ISTORE 2 + |__STACK: [] + |__LOCAL: [I, I, I] + L8 + ILOAD 2 + |__STACK: [I] + |__LOCAL: [I, I, I] + ILOAD 1 + |__STACK: [I, I] + |__LOCAL: [I, I, I] + IDIV + |__STACK: [I] + |__LOCAL: [I, I, I] + ISTORE 2 + |__STACK: [] + |__LOCAL: [I, I, I] + L9 + ILOAD 2 + |__STACK: [I] + |__LOCAL: [I, I, I] + ILOAD 1 + |__STACK: [I, I] + |__LOCAL: [I, I, I] + IREM + |__STACK: [I] + |__LOCAL: [I, I, I] + ISTORE 2 + |__STACK: [] + |__LOCAL: [I, I, I] + L10 + ILOAD 2 + |__STACK: [I] + |__LOCAL: [I, I, I] + ICONST_2 + |__STACK: [I, I] + |__LOCAL: [I, I, I] + ISHL + |__STACK: [I] + |__LOCAL: [I, I, I] + ISTORE 2 + |__STACK: [] + |__LOCAL: [I, I, I] + L11 + ILOAD 2 + |__STACK: [I] + |__LOCAL: [I, I, I] + ILOAD 1 + |__STACK: [I, I] + |__LOCAL: [I, I, I] + ISHR + |__STACK: [I] + |__LOCAL: [I, I, I] + ISTORE 2 + |__STACK: [] + |__LOCAL: [I, I, I] + L12 + ILOAD 2 + |__STACK: [I] + |__LOCAL: [I, I, I] + ICONST_3 + |__STACK: [I, I] + |__LOCAL: [I, I, I] + IUSHR + |__STACK: [I] + |__LOCAL: [I, I, I] + ISTORE 2 + |__STACK: [] + |__LOCAL: [I, I, I] + L13 + ILOAD 2 + |__STACK: [I] + |__LOCAL: [I, I, I] + I2L + |__STACK: [J, TOP] + |__LOCAL: [I, I, I] + LSTORE 3 + |__STACK: [] + |__LOCAL: [I, I, I, J, TOP] + L14 + LLOAD 3 + |__STACK: [J, TOP] + |__LOCAL: [I, I, I, J, TOP] + ILOAD 1 + |__STACK: [J, TOP, I] + |__LOCAL: [I, I, I, J, TOP] + LSHL + |__STACK: [J, TOP] + |__LOCAL: [I, I, I, J, TOP] + LSTORE 3 + |__STACK: [] + |__LOCAL: [I, I, I, J, TOP] + L15 + LLOAD 3 + |__STACK: [J, TOP] + |__LOCAL: [I, I, I, J, TOP] + L2I + |__STACK: [I] + |__LOCAL: [I, I, I, J, TOP] + IRETURN + |__STACK: [] + |__LOCAL: [I, I, I, J, TOP] +Method createNumberWithDiamond + L0 + ACONST_NULL + |__STACK: [NULL] + |__LOCAL: [I] + ASTORE 1 + |__STACK: [] + |__LOCAL: [I, NULL] + L1 + ILOAD 0 + |__STACK: [I] + |__LOCAL: [I, NULL] + IFEQ L2 + |__STACK: [] + |__LOCAL: [I, NULL] + L3 + NEW java/lang/Integer + |__STACK: [Ljava/lang/Integer;] + |__LOCAL: [I, NULL] + DUP + |__STACK: [Ljava/lang/Integer;, Ljava/lang/Integer;] + |__LOCAL: [I, NULL] + ICONST_1 + |__STACK: [Ljava/lang/Integer;, Ljava/lang/Integer;, I] + |__LOCAL: [I, NULL] + INVOKESPECIAL java/lang/Integer. (I)V + |__STACK: [Ljava/lang/Integer;] + |__LOCAL: [I, NULL] + ASTORE 1 + |__STACK: [] + |__LOCAL: [I, Ljava/lang/Integer;] + GOTO L4 + |__STACK: [] + |__LOCAL: [I, Ljava/lang/Integer;] + L2 + FRAME APPEND [java/lang/Number] + NEW java/lang/Double + |__STACK: [Ljava/lang/Double;] + |__LOCAL: [I, Ljava/lang/Number;] + DUP + |__STACK: [Ljava/lang/Double;, Ljava/lang/Double;] + |__LOCAL: [I, Ljava/lang/Number;] + DCONST_1 + |__STACK: [Ljava/lang/Double;, Ljava/lang/Double;, D, TOP] + |__LOCAL: [I, Ljava/lang/Number;] + INVOKESPECIAL java/lang/Double. (D)V + |__STACK: [Ljava/lang/Double;] + |__LOCAL: [I, Ljava/lang/Number;] + ASTORE 1 + |__STACK: [] + |__LOCAL: [I, Ljava/lang/Double;] + L4 + FRAME SAME + ALOAD 1 + |__STACK: [Ljava/lang/Number;] + |__LOCAL: [I, Ljava/lang/Number;] + ARETURN + |__STACK: [] + |__LOCAL: [I, Ljava/lang/Number;] +Method createMultiObjectArray + L0 + ICONST_0 + |__STACK: [I] + |__LOCAL: [] + ICONST_0 + |__STACK: [I, I] + |__LOCAL: [] + MULTIANEWARRAY [[Ljava/lang/Object; 2 + |__STACK: [[[Ljava/lang/Object;] + |__LOCAL: [] + ARETURN + |__STACK: [] + |__LOCAL: [] +Method createObjectArray + L0 + ICONST_0 + |__STACK: [I] + |__LOCAL: [] + ANEWARRAY java/lang/Object + |__STACK: [[Ljava/lang/Object;] + |__LOCAL: [] + ARETURN + |__STACK: [] + |__LOCAL: [] +Method createIntArray + L0 + ICONST_0 + |__STACK: [I] + |__LOCAL: [] + NEWARRAY T_INT + |__STACK: [[I] + |__LOCAL: [] + ARETURN + |__STACK: [] + |__LOCAL: [] +Method staticEmpty1 + L0 + RETURN + |__STACK: [] + |__LOCAL: [] +Method instanceEmpty1 + L0 + RETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;] +Method identity + L0 + ILOAD 0 + |__STACK: [I] + |__LOCAL: [I] + IRETURN + |__STACK: [] + |__LOCAL: [I] +Method identity2 + L0 + ILOAD 0 + |__STACK: [I] + |__LOCAL: [I] + ISTORE 1 + |__STACK: [] + |__LOCAL: [I, I] + L1 + ILOAD 1 + |__STACK: [I] + |__LOCAL: [I, I] + IRETURN + |__STACK: [] + |__LOCAL: [I, I] +Method readFile + L0 + NEW java/io/BufferedReader + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;] + NEW java/io/FileReader + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;] + ALOAD 1 + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;, Ljava/io/File;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;] + INVOKESPECIAL java/io/FileReader. (Ljava/io/File;)V + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;] + INVOKESPECIAL java/io/BufferedReader. (Ljava/io/Reader;)V + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;] + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;] + ACONST_NULL + |__STACK: [NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;] + ASTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL] + L1 + NEW java/io/BufferedReader + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL] + NEW java/io/FileReader + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL] + ALOAD 1 + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;, Ljava/io/File;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL] + INVOKESPECIAL java/io/FileReader. (Ljava/io/File;)V + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL] + INVOKESPECIAL java/io/BufferedReader. (Ljava/io/Reader;)V + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL] + ASTORE 4 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;] + L2 + ACONST_NULL + |__STACK: [NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;] + ASTORE 5 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + L3 + NEW java/io/BufferedReader + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + NEW java/io/FileReader + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + ALOAD 1 + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;, Ljava/io/File;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + INVOKESPECIAL java/io/FileReader. (Ljava/io/File;)V + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + INVOKESPECIAL java/io/BufferedReader. (Ljava/io/Reader;)V + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + ASTORE 6 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;] + L4 + ACONST_NULL + |__STACK: [NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;] + ASTORE 7 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + L5 + NEW java/io/BufferedReader + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + NEW java/io/FileReader + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + DUP + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + ALOAD 1 + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;, Ljava/io/FileReader;, Ljava/io/File;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + INVOKESPECIAL java/io/FileReader. (Ljava/io/File;)V + |__STACK: [Ljava/io/BufferedReader;, Ljava/io/BufferedReader;, Ljava/io/FileReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + INVOKESPECIAL java/io/BufferedReader. (Ljava/io/Reader;)V + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + ASTORE 8 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;] + L6 + ACONST_NULL + |__STACK: [NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;] + ASTORE 9 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + L7 + ALOAD 8 + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + IFNULL L8 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + ALOAD 9 + |__STACK: [NULL] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + IFNULL L9 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + L10 + ALOAD 8 + |__STACK: [Ljava/io/BufferedReader;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + L11 + GOTO L8 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL, Ljava/io/BufferedReader;, NULL] + 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: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 9 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 10 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + GOTO L8 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L9 + FRAME SAME + ALOAD 8 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L8 + FRAME CHOP 2 + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + IFNULL L13 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + ALOAD 7 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + IFNULL L14 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L15 + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L16 + GOTO L13 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L17 + FRAME SAME1 java/lang/Throwable + ASTORE 8 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 7 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 8 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + GOTO L13 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L14 + FRAME SAME + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + GOTO L13 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L18 + FRAME SAME1 java/lang/Throwable + ASTORE 8 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 8 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ASTORE 7 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 8 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L19 + FRAME SAME1 java/lang/Throwable + ASTORE 11 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + L20 + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + IFNULL L21 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + ALOAD 7 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + IFNULL L22 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + L23 + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + L24 + GOTO L21 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + 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: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 7 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 12 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + GOTO L21 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L22 + FRAME SAME + ALOAD 6 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + L21 + FRAME SAME + ALOAD 11 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, Ljava/lang/Throwable;] + 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;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + IFNULL L26 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + ALOAD 5 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + IFNULL L27 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L28 + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L29 + GOTO L26 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L30 + FRAME SAME1 java/lang/Throwable + ASTORE 6 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 5 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 6 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + GOTO L26 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L27 + FRAME SAME + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + GOTO L26 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L31 + FRAME SAME1 java/lang/Throwable + ASTORE 6 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 6 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ASTORE 5 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 6 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L32 + FRAME SAME1 java/lang/Throwable + ASTORE 13 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L33 + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + IFNULL L34 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + ALOAD 5 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + IFNULL L35 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L36 + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L37 + GOTO L34 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + 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: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 5 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 14 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + GOTO L34 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L35 + FRAME SAME + ALOAD 4 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L34 + FRAME SAME + ALOAD 13 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L26 + FRAME FULL [testsubjects/TestSubject java/io/File java/lang/AutoCloseable java/lang/Throwable] [] + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + IFNULL L39 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + IFNULL L40 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L41 + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L42 + GOTO L39 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L43 + FRAME SAME1 java/lang/Throwable + ASTORE 4 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 4 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + GOTO L39 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L40 + FRAME SAME + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + GOTO L39 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;] + L44 + FRAME SAME1 java/lang/Throwable + ASTORE 4 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 4 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ASTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 4 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L45 + FRAME SAME1 java/lang/Throwable + ASTORE 15 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L46 + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + IFNULL L47 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + IFNULL L48 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L49 + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L50 + GOTO L47 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + 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: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 3 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + ALOAD 16 + |__STACK: [Ljava/lang/Throwable;, Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + INVOKEVIRTUAL java/lang/Throwable.addSuppressed (Ljava/lang/Throwable;)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + GOTO L47 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;, Ljava/lang/Throwable;] + L48 + FRAME SAME + ALOAD 2 + |__STACK: [Ljava/lang/AutoCloseable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + INVOKEINTERFACE java/lang/AutoCloseable.close ()V (itf) + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L47 + FRAME SAME + ALOAD 15 + |__STACK: [Ljava/lang/Throwable;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + ATHROW + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/lang/AutoCloseable;, Ljava/lang/Throwable;, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, TOP, Ljava/lang/Throwable;] + L39 + FRAME FULL [testsubjects/TestSubject java/io/File] [] + GOTO L52 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;] + L53 + FRAME SAME1 java/io/IOException + ASTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/IOException;] + L54 + ALOAD 2 + |__STACK: [Ljava/io/IOException;] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/IOException;] + INVOKEVIRTUAL java/io/IOException.printStackTrace ()V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;, Ljava/io/IOException;] + L52 + FRAME SAME + RETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, Ljava/io/File;] +Method testWithDoubleTypes + L0 + DCONST_1 + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;] + DSTORE 1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP] + L1 + DCONST_1 + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP] + DSTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + L2 + FRAME APPEND [D D] + DLOAD 3 + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + LDC 22.0 + |__STACK: [D, TOP, D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + DCMPG + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + IFGE L3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + L4 + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + |__STACK: [Ljava/io/PrintStream;] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + DLOAD 3 + |__STACK: [Ljava/io/PrintStream;, D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + INVOKEVIRTUAL java/io/PrintStream.println (D)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + L5 + DLOAD 1 + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + DLOAD 3 + |__STACK: [D, TOP, D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + DADD + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + DSTORE 1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + L6 + DLOAD 3 + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + DCONST_1 + |__STACK: [D, TOP, D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + DADD + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + DSTORE 3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + GOTO L2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP, D, TOP] + L3 + FRAME CHOP 1 + DLOAD 1 + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP] + DRETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, D, TOP] +Method testWithFloatAndDoubleTypes + L0 + FCONST_1 + |__STACK: [F] + |__LOCAL: [Ltestsubjects/TestSubject;] + FSTORE 1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, F] + L1 + DCONST_1 + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, F] + DSTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + L2 + FRAME APPEND [F D] + DLOAD 2 + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + LDC 22.0 + |__STACK: [D, TOP, D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + DCMPG + |__STACK: [I] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + IFGE L3 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + L4 + GETSTATIC java/lang/System.out : Ljava/io/PrintStream; + |__STACK: [Ljava/io/PrintStream;] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + DLOAD 2 + |__STACK: [Ljava/io/PrintStream;, D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + INVOKEVIRTUAL java/io/PrintStream.println (D)V + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + L5 + FLOAD 1 + |__STACK: [F] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + DLOAD 2 + |__STACK: [F, D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + D2F + |__STACK: [F, F] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + FADD + |__STACK: [F] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + FSTORE 1 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + L6 + DLOAD 2 + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + DCONST_1 + |__STACK: [D, TOP, D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + DADD + |__STACK: [D, TOP] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + DSTORE 2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + GOTO L2 + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, F, D, TOP] + L3 + FRAME CHOP 1 + FLOAD 1 + |__STACK: [F] + |__LOCAL: [Ltestsubjects/TestSubject;, F] + FRETURN + |__STACK: [] + |__LOCAL: [Ltestsubjects/TestSubject;, F] +Method + L0 + ICONST_1 + |__STACK: [I] + |__LOCAL: [] + PUTSTATIC testsubjects/TestSubject.VALUE_ONE : I + |__STACK: [] + |__LOCAL: [] + L1 + ICONST_2 + |__STACK: [I] + |__LOCAL: [] + PUTSTATIC testsubjects/TestSubject.VALUE_TWO : I + |__STACK: [] + |__LOCAL: [] + RETURN + |__STACK: [] + |__LOCAL: [] diff --git a/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java b/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java new file mode 100644 index 00000000000000..6df400873f7ae0 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/BytecodeTypeInferenceTest.java @@ -0,0 +1,71 @@ +// 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.ImmutableMap; +import com.google.common.io.Files; +import com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType; +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 testTypeInference() 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(); + assertThat(inferenceResult).isEqualTo(golden); + } + + @Test + public void testInferType() { + ImmutableMap map = + ImmutableMap.builder() + .put("Z", InferredType.BOOLEAN) + .put("B", InferredType.BYTE) + .put("I", InferredType.INT) + .put("F", InferredType.FLOAT) + .put("D", InferredType.DOUBLE) + .put("J", InferredType.LONG) + .put("TOP", InferredType.TOP) + .put("NULL", InferredType.NULL) + .put("UNINITIALIZED_THIS", InferredType.UNINITIALIZED_THIS) + .build(); + map.forEach( + (descriptor, expected) -> { + InferredType type = InferredType.create(descriptor); + assertThat(type.descriptor()).isEqualTo(descriptor); + assertThat(type).isSameInstanceAs(expected); + }); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/ClassSignatureParserTest.java b/src/test/java/com/google/devtools/build/android/desugar/ClassSignatureParserTest.java new file mode 100644 index 00000000000000..5e8a82c3f18e17 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/ClassSignatureParserTest.java @@ -0,0 +1,188 @@ +// Copyright 2019 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.ClassSignatureParser.ClassSignature; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link ClassSignatureParser} based on $4.7.9.1 + * of the Java Virtual Machine Specification. + */ +@RunWith(JUnit4.class) +public class ClassSignatureParserTest { + + @Test + public void superclass_noGenerics() { + ClassSignature classSignature = + ClassSignatureParser.readTypeParametersForInterfaces( + "Foo", "Ljava/lang/Object;", "Object", new String[] {}); + + assertThat(classSignature.superClassSignature().identifier()).isEqualTo("Ljava/lang/Object"); + assertThat(classSignature.superClassSignature().typeParameters()).isEmpty(); + assertThat(classSignature.typeParameters()).isEmpty(); + assertThat(classSignature.interfaceTypeParameters()).isEmpty(); + } + + @Test + public void superClass_oneGenericParameter() { + ClassSignature classSignature = + ClassSignatureParser.readTypeParametersForInterfaces( + "Foo", + "Ljava/util/LinkedHashSet;", + "LinkedHashSet", + new String[] {}); + + assertThat(classSignature.superClassSignature().identifier()) + .isEqualTo("Ljava/util/LinkedHashSet"); + assertThat(classSignature.superClassSignature().typeParameters()) + .isEqualTo(""); + assertThat(classSignature.typeParameters()).isEmpty(); + assertThat(classSignature.interfaceTypeParameters()).isEmpty(); + } + + @Test + public void superClass_twoGenericParameters() { + ClassSignature classSignature = + ClassSignatureParser.readTypeParametersForInterfaces( + "Foo", + "Ljava/util/concurrent/ConcurrentSkipListMap;", + "ConcurrentSkipListMap", + new String[] {}); + + assertThat(classSignature.superClassSignature().identifier()) + .isEqualTo("Ljava/util/concurrent/ConcurrentSkipListMap"); + assertThat(classSignature.superClassSignature().typeParameters()) + .isEqualTo(""); + assertThat(classSignature.typeParameters()).isEmpty(); + assertThat(classSignature.interfaceTypeParameters()).isEmpty(); + } + + @Test + public void superClass_nestedClass() { + ClassSignature classSignature = + ClassSignatureParser.readTypeParametersForInterfaces( + "Foo", + "Lj$/util/stream/SpinedBuffer$OfPrimitive" + + "" + + ".BaseSpliterator;" + + "Lj$/util/Spliterator$OfDouble;", + "BaseSpliterator", + new String[] {"Spliterator$OfDouble"}); + + assertThat(classSignature.superClassSignature().identifier()) + .isEqualTo( + "Lj$/util/stream/SpinedBuffer$OfPrimitive" + + "" + + ".BaseSpliterator"); + assertThat(classSignature.superClassSignature().typeParameters()) + .isEqualTo(""); + assertThat(classSignature.typeParameters()).isEmpty(); + assertThat(classSignature.interfaceTypeParameters()).containsExactly(""); + } + + @Test + public void genericBounds_oneBound() { + ClassSignature classSignature = + ClassSignatureParser.readTypeParametersForInterfaces( + "Foo", + "Ljava/util/AbstractList;", + "AbstractList", + new String[] {}); + + assertThat(classSignature.superClassSignature().identifier()) + .isEqualTo("Ljava/util/AbstractList"); + assertThat(classSignature.superClassSignature().typeParameters()).isEqualTo(""); + assertThat(classSignature.typeParameters()).isEqualTo(""); + assertThat(classSignature.interfaceTypeParameters()).isEmpty(); + } + + @Test + public void genericBounds_twoBounds() { + ClassSignature classSignature = + ClassSignatureParser.readTypeParametersForInterfaces( + "Foo", + ";>" + + "Ljava/lang/Object;Ljava/util/List;", + "Object", + new String[] {"List"}); + + assertThat(classSignature.superClassSignature().identifier()).isEqualTo("Ljava/lang/Object"); + assertThat(classSignature.superClassSignature().typeParameters()).isEmpty(); + assertThat(classSignature.typeParameters()) + .isEqualTo(";>"); + assertThat(classSignature.interfaceTypeParameters()).containsExactly(""); + } + + @Test + public void interfaces_oneImplementation() { + ClassSignature classSignature = + ClassSignatureParser.readTypeParametersForInterfaces( + "Foo", + "Ljava/lang/Object;" + "Ljava/util/List;", + "Object", + new String[] {"List"}); + + assertThat(classSignature.superClassSignature().identifier()).isEqualTo("Ljava/lang/Object"); + assertThat(classSignature.superClassSignature().typeParameters()).isEmpty(); + assertThat(classSignature.typeParameters()).isEqualTo(""); + assertThat(classSignature.interfaceTypeParameters()).containsExactly(""); + } + + @Test + public void interfaces_twoImplementations() { + ClassSignature classSignature = + ClassSignatureParser.readTypeParametersForInterfaces( + "Foo", + "Ljava/lang/Object;" + + "Ljava/util/List;Ljava/util/Set;", + "Object", + new String[] {"List", "Set"}); + + assertThat(classSignature.superClassSignature().identifier()).isEqualTo("Ljava/lang/Object"); + assertThat(classSignature.superClassSignature().typeParameters()).isEmpty(); + assertThat(classSignature.typeParameters()) + .isEqualTo(""); + assertThat(classSignature.interfaceTypeParameters()) + .containsExactly("", "") + .inOrder(); + } + + @Test + public void interface_nestedGenerics() { + ClassSignature classSignature = + ClassSignatureParser.readTypeParametersForInterfaces( + "Foo", + "Ljava/lang/Object;Ldagger/internal/Factory" + + ";" + + ">;", + "Object", + new String[] {"Factory"}); + + assertThat(classSignature.superClassSignature().identifier()).isEqualTo("Ljava/lang/Object"); + assertThat(classSignature.superClassSignature().typeParameters()).isEmpty(); + assertThat(classSignature.typeParameters()).isEmpty(); + assertThat(classSignature.interfaceTypeParameters()) + .containsExactly( + ";" + + ">"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java b/src/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java new file mode 100644 index 00000000000000..9d6955305b24fb --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/CoreLibrarySupportTest.java @@ -0,0 +1,528 @@ +// Copyright 2018 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.io.CoreLibraryRewriter; +import com.google.devtools.build.android.desugar.langmodel.MethodId; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocation; +import com.google.devtools.build.android.desugar.retarget.ClassMemberRetargetConfig; +import com.google.devtools.build.android.desugar.retarget.MethodInvocationReplacement; +import com.google.devtools.build.android.desugar.retarget.ReplacementRange; +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.Opcodes; + +/** Tests for {@link CoreLibrarySupport}. */ +@RunWith(JUnit4.class) +public class CoreLibrarySupportTest { + + @Test + public void testIsRenamedCoreLibrary() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + null, + ImmutableList.of("java/time/"), + ImmutableList.of(), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat(support.isRenamedCoreLibrary("java/time/X")).isTrue(); + assertThat(support.isRenamedCoreLibrary("java/time/y/X")).isTrue(); + assertThat(support.isRenamedCoreLibrary("java/io/X")).isFalse(); + assertThat(support.isRenamedCoreLibrary("java/io/X$$CC")).isTrue(); + assertThat(support.isRenamedCoreLibrary("java/io/X$$Lambda$17")).isTrue(); + assertThat(support.isRenamedCoreLibrary("com/google/X")).isFalse(); + } + + @Test + public void testIsRenamedCoreLibrary_prefixedLoader() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter("__/"), + null, + ImmutableList.of("java/time/"), + ImmutableList.of(), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat(support.isRenamedCoreLibrary("__/java/time/X")).isTrue(); + assertThat(support.isRenamedCoreLibrary("__/java/time/y/X")).isTrue(); + assertThat(support.isRenamedCoreLibrary("__/java/io/X")).isFalse(); + assertThat(support.isRenamedCoreLibrary("__/java/io/X$$CC")).isTrue(); + assertThat(support.isRenamedCoreLibrary("__/java/io/X$$Lambda$17")).isTrue(); + assertThat(support.isRenamedCoreLibrary("com/google/X")).isFalse(); + } + + @Test + public void testRenameCoreLibrary() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + null, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat(support.renameCoreLibrary("java/time/X")).isEqualTo("j$/time/X"); + assertThat(support.renameCoreLibrary("com/google/X")).isEqualTo("com/google/X"); + } + + @Test + public void testRenameCoreLibrary_prefixedLoader() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter("__/"), + null, + ImmutableList.of(), + ImmutableList.of(), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat(support.renameCoreLibrary("__/java/time/X")).isEqualTo("j$/time/X"); + assertThat(support.renameCoreLibrary("com/google/X")).isEqualTo("com/google/X"); + } + + @Test + public void testMoveTarget() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter("__desugar__/"), + null, + ImmutableList.of("java/util/Helper"), + ImmutableList.of(), + ImmutableList.of(), + ClassMemberRetargetConfig.builder() + .addInProcessReplacement( + MethodInvocationReplacement.newBuilder() + .setSource( + MethodInvocation.newBuilder() + .setOpcode(182) + .setIsInterface(false) + .setMethodId( + MethodId.newBuilder() + .setOwner("java/util/Existing") + .setName("match") + .setDesc("()V"))) + .setDestination( + MethodInvocation.newBuilder() + .setOpcode(182) + .setIsInterface(false) + .setMethodId( + MethodId.newBuilder() + .setOwner("java/util/DesugarHelper") + .setName("match") + .setDesc("()V"))) + .setAutoDeduceOpcodeAndDesc(true) + .addRange(ReplacementRange.ALL) + .build()) + .addInProcessReplacement( + MethodInvocationReplacement.newBuilder() + .setSource( + MethodInvocation.newBuilder() + .setOpcode(182) + .setIsInterface(false) + .setMethodId( + MethodId.newBuilder() + .setOwner("java/util/Existing") + .setName("unused") + .setDesc("()V"))) + .setDestination( + MethodInvocation.newBuilder() + .setOpcode(182) + .setIsInterface(false) + .setMethodId( + MethodId.newBuilder() + .setOwner("com/google/Unused") + .setName("unused") + .setDesc("()V"))) + .addRange(ReplacementRange.ALL) + .build()) + .addEnabledInvocationReplacementRange(ReplacementRange.ALL) + .build()); + + assertThat(support.getMoveTarget("__desugar__/java/util/Existing", "match", "()V")) + .isEqualTo("j$/util/DesugarHelper"); + + assertThat(support.getMoveTarget("java/util/Existing", "match", "()V")) + .isEqualTo("j$/util/DesugarHelper"); + assertThat(support.getMoveTarget("__desugar__/java/util/Existing", "matchesnot", "()V")) + .isNull(); + assertThat(support.getMoveTarget("__desugar__/java/util/ExistingOther", "match", "()V")) + .isNull(); + assertThat(support.usedRuntimeHelpers()).containsExactly("j$/util/DesugarHelper"); + } + + @Test + public void testIsEmulatedCoreClassOrInterface() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + Thread.currentThread().getContextClassLoader(), + ImmutableList.of("java/util/concurrent/"), + ImmutableList.of("java/util/Map"), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat(support.isEmulatedCoreClassOrInterface("java/util/Map")).isTrue(); + assertThat(support.isEmulatedCoreClassOrInterface("java/util/Map$$Lambda$17")).isFalse(); + assertThat(support.isEmulatedCoreClassOrInterface("java/util/Map$$CC")).isFalse(); + assertThat(support.isEmulatedCoreClassOrInterface("java/util/HashMap")).isTrue(); + assertThat(support.isEmulatedCoreClassOrInterface("java/util/concurrent/ConcurrentMap")) + .isFalse(); // false for renamed prefixes + assertThat(support.isEmulatedCoreClassOrInterface("com/google/Map")).isFalse(); + } + + @Test + public void testGetCoreInterfaceRewritingTarget_emulatedDefaultMethod() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + Thread.currentThread().getContextClassLoader(), + ImmutableList.of(), + ImmutableList.of("java/util/Collection"), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "java/util/Collection", + "removeIf", + "(Ljava/util/function/Predicate;)Z", + true)) + .isEqualTo(Collection.class); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEVIRTUAL, + "java/util/ArrayList", + "removeIf", + "(Ljava/util/function/Predicate;)Z", + false)) + .isEqualTo(Collection.class); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "com/google/HypotheticalListInterface", + "removeIf", + "(Ljava/util/function/Predicate;)Z", + true)) + .isNull(); + } + + @Test + public void testGetCoreInterfaceRewritingTarget_emulatedImplementationMoved() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + Thread.currentThread().getContextClassLoader(), + ImmutableList.of("java/util/Moved"), + ImmutableList.of("java/util/Map"), + ImmutableList.of(), + ClassMemberRetargetConfig.builder() + .addInProcessReplacement( + MethodInvocationReplacement.newBuilder() + .setSource( + MethodInvocation.newBuilder() + .setIsInterface(false) + .setOpcode(182) + .setMethodId( + MethodId.newBuilder() + .setOwner("java/util/LinkedHashMap") + .setName("forEach") + .setDesc("(Ljava/util/function/BiConsumer;)V"))) + .setDestination( + MethodInvocation.newBuilder() + .setIsInterface(false) + .setOpcode(182) + .setMethodId( + MethodId.newBuilder() + .setOwner("java/util/Moved") + .setName("forEach") + .setDesc("(Ljava/util/function/BiConsumer;)V"))) + .addRange(ReplacementRange.ALL) + .build()) + .addEnabledInvocationReplacementRange(ReplacementRange.ALL) + .build()); + + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "java/util/Map", + "forEach", + "(Ljava/util/function/BiConsumer;)V", + true)) + .isEqualTo(Map.class); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKESPECIAL, + "java/util/Map", + "forEach", + "(Ljava/util/function/BiConsumer;)V", + true)) + .isEqualTo(Map.class); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEVIRTUAL, + "java/util/LinkedHashMap", + "forEach", + "(Ljava/util/function/BiConsumer;)V", + false)) + .isEqualTo(Map.class); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKESPECIAL, + "java/util/LinkedHashMap", + "forEach", + "(Ljava/util/function/BiConsumer;)V", + false)) + .isEqualTo(LinkedHashMap.class); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKESPECIAL, + "java/util/HashMap", + "forEach", + "(Ljava/util/function/BiConsumer;)V", + false)) + .isEqualTo(Map.class); + } + + @Test + public void testGetCoreInterfaceRewritingTarget_abstractMethod() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + Thread.currentThread().getContextClassLoader(), + ImmutableList.of(), + ImmutableList.of("java/util/Collection"), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, "java/util/Collection", "size", "()I", true)) + .isNull(); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEVIRTUAL, "java/util/ArrayList", "size", "()I", false)) + .isNull(); + } + + @Test + public void testGetCoreInterfaceRewritingTarget_emulatedDefaultOverride() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + Thread.currentThread().getContextClassLoader(), + ImmutableList.of(), + ImmutableList.of("java/util/Map"), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "java/util/Map", + "putIfAbsent", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + true)) + .isEqualTo(Map.class); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "java/util/concurrent/ConcurrentMap", + "putIfAbsent", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + true)) + .isNull(); // putIfAbsent is default in Map but abstract in ConcurrentMap + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "java/util/concurrent/ConcurrentMap", + "getOrDefault", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + true)) + .isEqualTo(ConcurrentMap.class); // conversely, getOrDefault is overridden as default method + } + + @Test + public void testGetCoreInterfaceRewritingTarget_staticInterfaceMethod() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + Thread.currentThread().getContextClassLoader(), + ImmutableList.of(), + ImmutableList.of("java/util/Comparator"), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKESTATIC, + "java/util/Comparator", + "reverseOrder", + "()Ljava/util/Comparator;", + true)) + .isEqualTo(Comparator.class); + } + + /** + * Tests that call sites of renamed core libraries are treated like call sites in regular {@link + * InterfaceDesugaring}. + */ + @Test + public void testGetCoreInterfaceRewritingTarget_renamed() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + Thread.currentThread().getContextClassLoader(), + ImmutableList.of("java/util/"), + ImmutableList.of(), + ImmutableList.of(), + /* retargetConfig= */ null); + + // regular invocations of default methods: ignored + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "java/util/Collection", + "removeIf", + "(Ljava/util/function/Predicate;)Z", + true)) + .isNull(); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEVIRTUAL, + "java/util/ArrayList", + "removeIf", + "(Ljava/util/function/Predicate;)Z", + false)) + .isNull(); + + // abstract methods: ignored + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, "java/util/Collection", "size", "()I", true)) + .isNull(); + + // static interface method + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKESTATIC, + "java/util/Comparator", + "reverseOrder", + "()Ljava/util/Comparator;", + true)) + .isEqualTo(Comparator.class); + + // invokespecial for default methods: find nearest definition + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKESPECIAL, + "java/util/List", + "removeIf", + "(Ljava/util/function/Predicate;)Z", + true)) + .isEqualTo(Collection.class); + // invokespecial on a class: ignore (even if there's an inherited default method) + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKESPECIAL, + "java/util/ArrayList", + "removeIf", + "(Ljava/util/function/Predicate;)Z", + false)) + .isNull(); + } + + @Test + public void testGetCoreInterfaceRewritingTarget_ignoreRenamedInvokeInterface() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + Thread.currentThread().getContextClassLoader(), + ImmutableList.of("java/util/concurrent/"), // should return null for these + ImmutableList.of("java/util/Map"), + ImmutableList.of(), + /* retargetConfig= */ null); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "java/util/Map", + "getOrDefault", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + true)) + .isEqualTo(Map.class); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "java/util/concurrent/ConcurrentMap", + "getOrDefault", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + true)) + .isNull(); + } + + @Test + public void testGetCoreInterfaceRewritingTarget_excludedMethodIgnored() throws Exception { + CoreLibrarySupport support = + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + Thread.currentThread().getContextClassLoader(), + ImmutableList.of(), + ImmutableList.of("java/util/Collection"), + ImmutableList.of("java/util/Collection#removeIf"), + /* retargetConfig= */ null); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEINTERFACE, + "java/util/List", + "removeIf", + "(Ljava/util/function/Predicate;)Z", + true)) + .isNull(); + assertThat( + support.getCoreInterfaceRewritingTarget( + Opcodes.INVOKEVIRTUAL, + "java/util/ArrayList", + "removeIf", + "(Ljava/util/function/Predicate;)Z", + false)) + .isNull(); + } + + @Test + public void testEmulatedMethod_nullExceptions() throws Exception { + CoreLibrarySupport.EmulatedMethod m = + CoreLibrarySupport.EmulatedMethod.create(1, Number.class, "a", "()V", null); + assertThat(m.access()).isEqualTo(1); + assertThat(m.owner()).isEqualTo(Number.class); + assertThat(m.name()).isEqualTo("a"); + assertThat(m.descriptor()).isEqualTo("()V"); + assertThat(m.exceptions()).isEmpty(); + } + + @Test + public void testEmulatedMethod_givenExceptions() throws Exception { + CoreLibrarySupport.EmulatedMethod m = + CoreLibrarySupport.EmulatedMethod.create( + 1, Number.class, "a", "()V", new String[] {"b", "c"}); + assertThat(m.access()).isEqualTo(1); + assertThat(m.owner()).isEqualTo(Number.class); + assertThat(m.name()).isEqualTo("a"); + assertThat(m.descriptor()).isEqualTo("()V"); + assertThat(m.exceptions()).containsExactly("b", "c").inOrder(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java b/src/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java new file mode 100644 index 00000000000000..af09da76657b74 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/CorePackageRenamerTest.java @@ -0,0 +1,146 @@ +// Copyright 2018 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.io.CoreLibraryRewriter; +import org.objectweb.asm.Opcodes; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** Tests for {@link CorePackageRenamer}. */ +// TODO(b/134636762): Test override preservation logic somehow (needs to class-load test input) +@RunWith(JUnit4.class) +public class CorePackageRenamerTest { + + @Test + public void testSymbolRewrite() throws Exception { + MockClassVisitor out = new MockClassVisitor(); + CorePackageRenamer renamer = + new CorePackageRenamer( + out, + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + null, + ImmutableList.of("java/time/"), + ImmutableList.of(), + ImmutableList.of(), + /* retargetConfig= */ null)); + MethodVisitor mv = renamer.visitMethod(0, "test", "()V", null, null); + + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, "java/time/Instant", "now", "()Ljava/time/Instant;", false); + assertThat(out.mv.owner).isEqualTo("j$/time/Instant"); + assertThat(out.mv.desc).isEqualTo("()Lj$/time/Instant;"); + + // Ignore moved methods but not their descriptors + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/A", "m", "()Ljava/time/Instant;", false); + assertThat(out.mv.owner).isEqualTo("java/util/A"); + assertThat(out.mv.desc).isEqualTo("()Lj$/time/Instant;"); + + // Ignore arbitrary other methods but not their descriptors + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, "other/time/Instant", "now", "()Ljava/time/Instant;", false); + assertThat(out.mv.owner).isEqualTo("other/time/Instant"); + assertThat(out.mv.desc).isEqualTo("()Lj$/time/Instant;"); + + mv.visitFieldInsn(Opcodes.GETFIELD, "other/time/Instant", "now", "Ljava/time/Instant;"); + assertThat(out.mv.owner).isEqualTo("other/time/Instant"); + assertThat(out.mv.desc).isEqualTo("Lj$/time/Instant;"); + } + + @Test + public void testCorePackageCheck() throws Exception { + MockClassVisitor out = new MockClassVisitor(); + CorePackageRenamer renamer = + new CorePackageRenamer( + out, + new CoreLibrarySupport( + new CoreLibraryRewriter(""), + null, + ImmutableList.of("java/time/"), + ImmutableList.of(), + ImmutableList.of(), + /* retargetConfig= */ null)); + MethodVisitor mv = renamer.visitMethod(0, "test", "()V", null, null); + + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, "android/support/Instant", "now", "()Ljava/time/Instant;", false); + assertThat(out.mv.owner).isEqualTo("android/support/Instant"); + assertThat(out.mv.desc).isEqualTo("()Lj$/time/Instant;"); + + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, "android/arch/Instant", "now", "()Ljava/time/Instant;", false); + assertThat(out.mv.owner).isEqualTo("android/arch/Instant"); + assertThat(out.mv.desc).isEqualTo("()Lj$/time/Instant;"); + + try { + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, "android/time/Instant", "now", "()Ljava/time/Instant;", false); + Assert.fail("expected failure"); + } catch (IllegalStateException e) { + // expected + } + try { + mv.visitFieldInsn(Opcodes.GETFIELD, "android/time/Instant", "now", "Ljava/time/Instant;"); + Assert.fail("expected failure"); + } catch (IllegalStateException e) { + // expected + } + } + + private static class MockClassVisitor extends ClassVisitor { + + final MockMethodVisitor mv = new MockMethodVisitor(); + + public MockClassVisitor() { + super(Opcodes.ASM9); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + return mv; + } + } + + private static class MockMethodVisitor extends MethodVisitor { + + String owner; + String desc; + + public MockMethodVisitor() { + super(Opcodes.ASM9); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + this.owner = owner; + this.desc = desc; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + this.owner = owner; + this.desc = desc; + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixerTest.java b/src/test/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixerTest.java new file mode 100644 index 00000000000000..3b455eb3149f30 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixerTest.java @@ -0,0 +1,246 @@ +// 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.truth.Truth.assertThat; +import static com.google.devtools.build.android.desugar.DefaultMethodClassFixer.SubtypeComparator.INSTANCE; + +import com.google.common.collect.ImmutableList; +import com.google.common.io.Closer; +import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter; +import com.google.devtools.build.android.desugar.io.HeaderClassLoader; +import com.google.devtools.build.android.desugar.io.IndexedInputs; +import com.google.devtools.build.android.desugar.io.ThrowingClassLoader; +import com.google.devtools.build.android.r8.DependencyCollector; +import org.objectweb.asm.Opcodes; +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.TreeSet; +import java.util.concurrent.Callable; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** Unit Test for {@link DefaultMethodClassFixer} */ +@RunWith(JUnit4.class) +public class DefaultMethodClassFixerTest { + + private ClassReaderFactory classpathReader; + private ClassReaderFactory bootclassPath; + private ClassLoader classLoader; + private Closer closer; + + @Before + public void setup() throws IOException { + closer = Closer.create(); + CoreLibraryRewriter rewriter = new CoreLibraryRewriter(""); + + IndexedInputs indexedInputs = + toIndexedInputs(closer, System.getProperty("DefaultMethodClassFixerTest.input")); + IndexedInputs indexedClasspath = + toIndexedInputs(closer, System.getProperty("DefaultMethodClassFixerTest.classpath")); + IndexedInputs indexedBootclasspath = + toIndexedInputs(closer, System.getProperty("DefaultMethodClassFixerTest.bootclasspath")); + + bootclassPath = new ClassReaderFactory(indexedBootclasspath, rewriter); + IndexedInputs indexedClasspathAndInputFiles = indexedClasspath.withParent(indexedInputs); + classpathReader = new ClassReaderFactory(indexedClasspathAndInputFiles, rewriter); + ClassLoader bootclassloader = + new HeaderClassLoader(indexedBootclasspath, rewriter, new ThrowingClassLoader()); + classLoader = new HeaderClassLoader(indexedClasspathAndInputFiles, rewriter, bootclassloader); + } + + @After + public void teardown() throws IOException { + closer.close(); + } + + private static IndexedInputs toIndexedInputs(Closer closer, String stringPathList) + throws IOException { + final List pathList = readPathListFromString(stringPathList); + return new IndexedInputs(Desugar.toRegisteredInputFileProvider(closer, pathList)); + } + + private static List readPathListFromString(String pathList) { + return Arrays.stream(checkNotNull(pathList).split(File.pathSeparator)) + .map(Paths::get) + .collect(ImmutableList.toImmutableList()); + } + + private byte[] desugar(String classname) { + ClassReader reader = classpathReader.readIfKnown(classname); + return desugar(reader); + } + + private byte[] desugar(ClassReader reader) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + DefaultMethodClassFixer fixer = + new DefaultMethodClassFixer( + writer, + /*useGeneratedBaseClasses=*/ false, + classpathReader, + DependencyCollector.NoWriteCollectors.FAIL_ON_MISSING, + /*coreLibrarySupport=*/ null, + bootclassPath, + classLoader); + reader.accept(fixer, 0); + return writer.toByteArray(); + } + + private byte[] desugar(byte[] classContent) { + ClassReader reader = new ClassReader(classContent); + return desugar(reader); + } + + @Test + public void testDesugaringDirectImplementation() { + byte[] desugaredClass = + desugar( + ("com.google.devtools.build.android.desugar.testdata.java8." + + "DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetOne$C") + .replace('.', '/')); + checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC(desugaredClass); + + byte[] desugaredClassAgain = desugar(desugaredClass); + checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC( + desugaredClassAgain); + + desugaredClassAgain = desugar(desugaredClassAgain); + checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC( + desugar(desugaredClassAgain)); + } + + private void checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC( + byte[] classContent) { + ClassReader reader = new ClassReader(classContent); + reader.accept( + new ClassVisitor(Opcodes.ASM9) { + + class ClinitMethod extends MethodNode { + + public ClinitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + super(Opcodes.ASM9, access, name, desc, signature, exceptions); + } + } + + private ClinitMethod clinit; + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if ("".equals(name)) { + assertThat(clinit).isNull(); + clinit = new ClinitMethod(access, name, desc, signature, exceptions); + return clinit; + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + + @Override + public void visitEnd() { + assertThat(clinit).isNotNull(); + assertThat(clinit.instructions.size()).isEqualTo(3); + AbstractInsnNode instruction = clinit.instructions.getFirst(); + { + assertThat(instruction).isInstanceOf(MethodInsnNode.class); + MethodInsnNode field = (MethodInsnNode) instruction; + assertThat(field.owner) + .isEqualTo( + "com/google/devtools/build/android/desugar/testdata/java8/" + + "DefaultInterfaceMethodWithStaticInitializer" + + "$TestInterfaceSetOne$I1$$CC"); + assertThat(field.name) + .isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME); + assertThat(field.desc) + .isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC); + } + { + instruction = instruction.getNext(); + assertThat(instruction).isInstanceOf(MethodInsnNode.class); + MethodInsnNode field = (MethodInsnNode) instruction; + assertThat(field.owner) + .isEqualTo( + "com/google/devtools/build/android/desugar/testdata/java8/" + + "DefaultInterfaceMethodWithStaticInitializer" + + "$TestInterfaceSetOne$I2$$CC"); + assertThat(field.name) + .isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME); + assertThat(field.desc) + .isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC); + } + { + instruction = instruction.getNext(); + assertThat(instruction).isInstanceOf(InsnNode.class); + assertThat(instruction.getOpcode()).isEqualTo(Opcodes.RETURN); + } + } + }, + 0); + } + + @Test + public void testInterfaceComparator() { + assertThat(INSTANCE.compare(Runnable.class, Runnable.class)).isEqualTo(0); + assertThat(INSTANCE.compare(Runnable.class, MyRunnable1.class)).isEqualTo(1); + assertThat(INSTANCE.compare(MyRunnable2.class, Runnable.class)).isEqualTo(-1); + assertThat(INSTANCE.compare(MyRunnable3.class, Runnable.class)).isEqualTo(-1); + assertThat(INSTANCE.compare(MyRunnable1.class, MyRunnable3.class)).isEqualTo(1); + assertThat(INSTANCE.compare(MyRunnable3.class, MyRunnable2.class)).isEqualTo(-1); + assertThat(INSTANCE.compare(MyRunnable2.class, MyRunnable1.class)).isGreaterThan(0); + assertThat(INSTANCE.compare(Runnable.class, Serializable.class)).isGreaterThan(0); + assertThat(INSTANCE.compare(Serializable.class, Runnable.class)).isLessThan(0); + + TreeSet> orderedSet = new TreeSet<>(INSTANCE); + orderedSet.add(Serializable.class); + orderedSet.add(Runnable.class); + orderedSet.add(MyRunnable2.class); + orderedSet.add(Callable.class); + orderedSet.add(Serializable.class); + orderedSet.add(MyRunnable1.class); + orderedSet.add(MyRunnable3.class); + assertThat(orderedSet) + .containsExactly( + MyRunnable3.class, // subtype before supertype(s) + MyRunnable1.class, + MyRunnable2.class, + Serializable.class, // java... comes textually after com.google... + Runnable.class, + Callable.class) + .inOrder(); + } + + private static interface MyRunnable1 extends Runnable {} + + private static interface MyRunnable2 extends Runnable {} + + private static interface MyRunnable3 extends MyRunnable1, MyRunnable2 {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarCoreLibraryFunctionalTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarCoreLibraryFunctionalTest.java new file mode 100644 index 00000000000000..cebbfc94629390 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarCoreLibraryFunctionalTest.java @@ -0,0 +1,34 @@ +// 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 org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test that exercises classes in the {@code testdata} package. This is meant to be run against a + * desugared version of those classes, which in turn exercise various desugaring features. + */ +@RunWith(JUnit4.class) +public class DesugarCoreLibraryFunctionalTest { + + @Test + public void testAutoboxedTypeLambda() { + AutoboxedTypes.Lambda lambdaUse = AutoboxedTypes.autoboxedTypeLambda(1); + assertThat(lambdaUse.charAt("Karen")).isEqualTo("a"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarDefaultMethodsFunctionalTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarDefaultMethodsFunctionalTest.java new file mode 100644 index 00000000000000..703b8144896a01 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarDefaultMethodsFunctionalTest.java @@ -0,0 +1,235 @@ +// 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.Iterables; +import com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethodParameters; +import com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethodWithParam; +import com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethodWithParam.MethodInvocations; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Variant of {@link DesugarJava8FunctionalTest} that expects default and static interface methods + * to be desugared + */ +@RunWith(JUnit4.class) +public final class DesugarDefaultMethodsFunctionalTest extends DesugarJava8FunctionalTest { + + public DesugarDefaultMethodsFunctionalTest() { + super(/*expectBridgesFromSeparateTarget*/ true, /*expectDefaultMethods*/ false); + } + + @Test + public void staticMethodsAreAbsentFromInterfaceBytecode() { + List staticMethods = + Arrays.stream(InterfaceMethodParameters.class.getDeclaredMethods()) + .filter(method -> Modifier.isStatic(method.getModifiers())) + .collect(Collectors.toList()); + + assertThat(staticMethods).isEmpty(); + } + + @Test + public void defaultMethodsAreAbsentFromInterfaceBytecode() { + List defaultMethods = + Arrays.stream(InterfaceMethodParameters.class.getDeclaredMethods()) + .filter(Method::isDefault) + .collect(Collectors.toList()); + + assertThat(defaultMethods).isEmpty(); + } + + @Test + public void invokeInterfaceStaticMethod() { + assertThat(InterfaceMethodWithParam.MethodInvocations.simpleComputeStatic(1234)) + .isEqualTo(1234L); + } + + @Test + public void invokeInterfaceDefaultMethod() { + assertThat(InterfaceMethodWithParam.MethodInvocations.simpleComputeDefault(1234)) + .isEqualTo(1234L); + } + + @Test + public void parameterNamesArePreservedOnDesugaredDefaultMethods() throws Exception { + Method desugaredDefaultMethod = MethodInvocations.inspectDesugaredDefaultMethod(); + + assertThat(desugaredDefaultMethod.getParameters()[0].getName()) + .isEqualTo("v12525d61e4b10b3e27bc280dd61e56728e3e8c27"); + } + + @Test + public void parameterAnnotationsArePreservedOnDesugaredDefaultMethods() throws Exception { + Method desugaredDefaultMethod = MethodInvocations.inspectDesugaredDefaultMethod(); + + Annotation[][] parameterAnnotations = desugaredDefaultMethod.getParameterAnnotations(); + + assertThat(parameterAnnotations).hasLength(1); + assertThat(parameterAnnotations[0]).hasLength(1); + assertThat(parameterAnnotations[0][0]).isInstanceOf(InterfaceMethodWithParam.Foo.class); + } + + @Test + public void methodAnnotationsArePreservedOnDesugaredDefaultMethods() throws Exception { + Method method = MethodInvocations.inspectDesugaredDefaultMethod(); + + List annotations = Arrays.asList(method.getAnnotations()); + + Annotation parameterAnnotation = Iterables.getOnlyElement(annotations); + assertThat(parameterAnnotation).isInstanceOf(InterfaceMethodWithParam.Foo.class); + + InterfaceMethodWithParam.Foo fooInstance = (InterfaceMethodWithParam.Foo) parameterAnnotation; + assertThat(fooInstance.value()).isEqualTo("custom-attr-value-2"); + } + + @Test + public void methodReturnTypeAnnotationsArePreservedOnDesugaredDefaultMethods() throws Exception { + Method method = MethodInvocations.inspectDesugaredDefaultMethod(); + + List annotations = Arrays.asList(method.getAnnotatedReturnType().getAnnotations()); + + assertThat(Iterables.getOnlyElement(annotations)) + .isInstanceOf(InterfaceMethodWithParam.TyFoo.class); + } + + @Test + public void typeAnnotationsArePreservedOnDesugaredDefaultMethods() throws Exception { + Method desugaredDefaultMethod = MethodInvocations.inspectDesugaredDefaultMethod(); + + List parameters = Arrays.asList(desugaredDefaultMethod.getParameters()); + List typeAnnotations = + Arrays.asList(Iterables.getOnlyElement(parameters).getAnnotatedType().getAnnotations()); + + assertThat(Iterables.getOnlyElement(typeAnnotations)) + .isInstanceOf(InterfaceMethodWithParam.TyFoo.class); + } + + /** Test for b/129719629. */ + @Test + public void parameterNamesArePreservedOnStaticMethodCompanions() throws Exception { + Method method = InterfaceMethodWithParam.MethodInvocations.inspectCompanionOfStaticMethod(); + + assertThat(method.getParameters()).hasLength(1); + assertThat(method.getParameters()[0].getName()) + .isEqualTo("v4897b02fddeda3bb31bc15b3cad0f6febc61508"); + } + + @Test + public void parameterAnnotationsArePreservedOnStaticMethodCompanions() throws Exception { + Method method = InterfaceMethodWithParam.MethodInvocations.inspectCompanionOfStaticMethod(); + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + assertThat(parameterAnnotations).hasLength(1); + assertThat(parameterAnnotations[0]).hasLength(1); + assertThat(parameterAnnotations[0][0]).isInstanceOf(InterfaceMethodWithParam.Foo.class); + } + + @Test + public void methodParameterAnnotationsArePreservedOnStaticMethodCompanions() throws Exception { + Method method = InterfaceMethodWithParam.MethodInvocations.inspectCompanionOfStaticMethod(); + Annotation[] parameterAnnotations = method.getAnnotations(); + assertThat(parameterAnnotations).hasLength(1); + assertThat(parameterAnnotations[0]).isInstanceOf(InterfaceMethodWithParam.Foo.class); + } + + @Test + public void typeAnnotationsArePreservedOnStaticMethodCompanions() throws Exception { + Method method = InterfaceMethodWithParam.MethodInvocations.inspectCompanionOfStaticMethod(); + List parameterAnnotations = Arrays.asList(method.getParameters()); + + List typeAnnoations = + Arrays.asList( + Iterables.getOnlyElement(parameterAnnotations).getAnnotatedType().getAnnotations()); + + assertThat(Iterables.getOnlyElement(typeAnnoations)) + .isInstanceOf(InterfaceMethodWithParam.TyFoo.class); + } + + @Test + public void methodReturnTypeAnnotationsArePreservedOnStaticMethodCompanions() throws Exception { + Method method = InterfaceMethodWithParam.MethodInvocations.inspectCompanionOfStaticMethod(); + List returnTypeAnnotations = + Arrays.asList(method.getAnnotatedReturnType().getAnnotations()); + + assertThat(Iterables.getOnlyElement(returnTypeAnnotations)) + .isInstanceOf(InterfaceMethodWithParam.TyFoo.class); + } + + /** Test for b/129719629. */ + @Test + public void parameterNamesAreOmittedOnDefaultMethodCompanions() throws Exception { + Method method = + InterfaceMethodWithParam.MethodInvocations.inspectCompanionMethodOfDefaultMethod(); + + assertThat(method.getParameters()).hasLength(2); + assertThat(method.getParameters()[1].getName()) + .isNotEqualTo("v12525d61e4b10b3e27bc280dd61e56728e3e8c27"); + } + + @Test + public void parameterAnnotationsAreOmittedOnDefaultMethodCompanions() throws Exception { + Method method = + InterfaceMethodWithParam.MethodInvocations.inspectCompanionMethodOfDefaultMethod(); + + assertThat(method.getParameterAnnotations()).hasLength(2); + assertThat(method.getParameterAnnotations()[0]).isEmpty(); + assertThat(method.getParameterAnnotations()[1]).isEmpty(); + } + + @Test + public void methodAnnotationsArePreservedOnDefaultMethodCompanions() throws Exception { + Method method = + InterfaceMethodWithParam.MethodInvocations.inspectCompanionMethodOfDefaultMethod(); + + List annotations = Arrays.asList(method.getAnnotations()); + Annotation methodAnnotation = Iterables.getOnlyElement(annotations); + assertThat(methodAnnotation).isInstanceOf(InterfaceMethodWithParam.Foo.class); + + InterfaceMethodWithParam.Foo fooInstance = (InterfaceMethodWithParam.Foo) methodAnnotation; + assertThat(fooInstance.value()).isEqualTo("custom-attr-value-2"); + } + + @Test + public void methodReturnTypeAnnotationsAreOmittedOnDefaultMethodCompanions() throws Exception { + Method method = + InterfaceMethodWithParam.MethodInvocations.inspectCompanionMethodOfDefaultMethod(); + + Annotation[] methodReturnTypeAnnotations = method.getAnnotatedReturnType().getAnnotations(); + + assertThat(methodReturnTypeAnnotations).isEmpty(); + } + + @Test + public void parameterTypeAnnotationsAreOmittedOnDefaultMethodCompanion() throws Exception { + Method method = + InterfaceMethodWithParam.MethodInvocations.inspectCompanionMethodOfDefaultMethod(); + + List parameterAnnotations = Arrays.asList(method.getParameters()); + + assertThat(parameterAnnotations).hasSize(2); + assertThat(parameterAnnotations.get(0).getAnnotatedType().getAnnotations()).isEmpty(); + assertThat(parameterAnnotations.get(1).getAnnotatedType().getAnnotations()).isEmpty(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarFunctionalTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarFunctionalTest.java new file mode 100644 index 00000000000000..a5b2f87f9f4726 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarFunctionalTest.java @@ -0,0 +1,340 @@ +// 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 static com.google.common.truth.Truth.assertWithMessage; +import static java.lang.reflect.Modifier.isFinal; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteStreams; +import com.google.devtools.build.android.desugar.testdata.CaptureLambda; +import com.google.devtools.build.android.desugar.testdata.ConcreteFunction; +import com.google.devtools.build.android.desugar.testdata.ConstructorReference; +import com.google.devtools.build.android.desugar.testdata.GuavaLambda; +import com.google.devtools.build.android.desugar.testdata.InnerClassLambda; +import com.google.devtools.build.android.desugar.testdata.InterfaceWithLambda; +import com.google.devtools.build.android.desugar.testdata.Lambda; +import com.google.devtools.build.android.desugar.testdata.LambdaInOverride; +import com.google.devtools.build.android.desugar.testdata.MethodReference; +import com.google.devtools.build.android.desugar.testdata.MethodReferenceInSubclass; +import com.google.devtools.build.android.desugar.testdata.MethodReferenceSuperclass; +import com.google.devtools.build.android.desugar.testdata.OuterReferenceLambda; +import com.google.devtools.build.android.desugar.testdata.SpecializedFunction; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test that exercises classes in the {@code testdata} package. This is meant to be run against a + * desugared version of those classes, which in turn exercise various desugaring features. + */ +@RunWith(JUnit4.class) +public class DesugarFunctionalTest { + + private final int expectedBridgesFromSameTarget; + private final int expectedBridgesFromSeparateTarget; + private final boolean expectLambdaMethodsInInterfaces; + + public DesugarFunctionalTest() { + this(3, 1, false); + } + + /** Constructor for testing desugar while allowing default and static interface methods. */ + protected DesugarFunctionalTest( + boolean expectBridgesFromSeparateTarget, boolean expectDefaultMethods) { + this( + expectDefaultMethods ? 0 : 3, + expectBridgesFromSeparateTarget ? 1 : 0, + expectDefaultMethods); + } + + private DesugarFunctionalTest( + int bridgesFromSameTarget, int bridgesFromSeparateTarget, boolean lambdaMethodsInInterfaces) { + this.expectedBridgesFromSameTarget = bridgesFromSameTarget; + this.expectedBridgesFromSeparateTarget = bridgesFromSeparateTarget; + this.expectLambdaMethodsInInterfaces = lambdaMethodsInInterfaces; + } + + @Test + public void testGuavaLambda() { + GuavaLambda lambdaUse = new GuavaLambda(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(lambdaUse.as()).containsExactly("Alex"); + } + + @Test + public void testJavaLambda() { + Lambda lambdaUse = new Lambda(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(lambdaUse.as()).containsExactly("Alex"); + } + + @Test + public void testLambdaForIntersectionType() throws Exception { + assertThat(Lambda.hello().call()).isEqualTo("hello"); + } + + @Test + public void testCapturingLambda() { + CaptureLambda lambdaUse = new CaptureLambda(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(lambdaUse.prefixed("L")).containsExactly("Larry"); + } + + @Test + public void testOuterReferenceLambda() throws Exception { + OuterReferenceLambda lambdaUse = new OuterReferenceLambda(ImmutableList.of("Sergey", "Larry")); + assertThat(lambdaUse.filter(ImmutableList.of("Larry", "Alex"))).containsExactly("Larry"); + assertThat( + isFinal( + OuterReferenceLambda.class + .getDeclaredMethod("lambda$filter$0$OuterReferenceLambda", String.class) + .getModifiers())) + .isTrue(); + } + + /** + * Tests a lambda in a subclass whose generated lambda$ method has the same name and signature as + * a lambda$ method generated by Javac in a superclass and both of these methods are used in the + * implementation of the subclass (by calling super). Naively this leads to wrong behavior (in + * this case, return a non-empty list) because the lambda$ in the superclass is never used once + * its made non-private during desugaring. + */ + @Test + public void testOuterReferenceLambdaInOverride() throws Exception { + OuterReferenceLambda lambdaUse = new LambdaInOverride(ImmutableList.of("Sergey", "Larry")); + assertThat(lambdaUse.filter(ImmutableList.of("Larry", "Alex"))).isEmpty(); + assertThat( + isFinal( + LambdaInOverride.class + .getDeclaredMethod("lambda$filter$0$LambdaInOverride", String.class) + .getModifiers())) + .isTrue(); + } + + @Test + public void testLambdaInAnonymousClassReferencesSurroundingMethodParameter() throws Exception { + assertThat(Lambda.mult(21).apply(2).call()).isEqualTo(42); + } + + /** Tests a lambda that accesses a method parameter across 2 nested anonymous classes. */ + @Test + public void testLambdaInNestedAnonymousClass() throws Exception { + InnerClassLambda lambdaUse = new InnerClassLambda(ImmutableList.of("Sergey", "Larry")); + assertThat(lambdaUse.prefixFilter("L").apply(ImmutableList.of("Lois", "Larry")).call()) + .containsExactly("Larry"); + } + + @Test + public void testClassMethodReference() { + MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); + StringBuilder dest = new StringBuilder(); + methodrefUse.appendAll(dest); + assertThat(dest.toString()).isEqualTo("SergeyLarryAlex"); + } + + // Regression test for b/33378312 + @Test + public void testHiddenMethodReference() { + MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(methodrefUse.intersect(ImmutableList.of("Alex", "Sundar"))).containsExactly("Alex"); + } + + // Regression test for b/33378312 + @Test + public void testHiddenStaticMethodReference() { + MethodReference methodrefUse = + new MethodReference(ImmutableList.of("Sergey", "Larry", "Sundar")); + assertThat(methodrefUse.some()).containsExactly("Sergey", "Sundar"); + } + + @Test + public void testDuplicateHiddenMethodReference() { + MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(methodrefUse.onlyIn(ImmutableList.of("Alex", "Sundar"))).containsExactly("Sundar"); + } + + // Regression test for b/36201257 + @Test + public void testMethodReferenceThatNeedsBridgeInSubclass() { + MethodReferenceInSubclass methodrefUse = + new MethodReferenceInSubclass(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(methodrefUse.containsE()).containsExactly("Sergey", "Alex"); + assertThat(methodrefUse.startsWithL()).containsExactly("Larry"); + // Check to make sure sub- and superclass have bridge methods with matching descriptors but + // different names + Method superclassBridge = findOnlyBridge(MethodReferenceSuperclass.class); + Method subclassBridge = findOnlyBridge(MethodReferenceInSubclass.class); + assertThat(superclassBridge.getName()).isNotEqualTo(subclassBridge.getName()); + assertThat(superclassBridge.getParameterTypes()).isEqualTo(subclassBridge.getParameterTypes()); + } + + private Method findOnlyBridge(Class clazz) { + Method result = null; + for (Method m : clazz.getDeclaredMethods()) { + if (m.getName().startsWith("bridge$")) { + assertWithMessage(m.getName()).that(result).isNull(); + result = m; + } + } + assertWithMessage(clazz.getSimpleName()).that(result).isNotNull(); + return result; + } + + // Regression test for b/33378312 + @Test + public void testThrowingPrivateMethodReference() throws Exception { + MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry")); + Callable stringer = methodrefUse.stringer(); + try { + stringer.call(); + fail("IOException expected"); + } catch (IOException expected) { + assertThat(expected).hasMessageThat().isEqualTo("SergeyLarry"); + } catch (Exception e) { + throw e; + } + } + + // Regression test for b/33304582 + @Test + public void testInterfaceMethodReference() { + MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); + MethodReference.Transformer transform = + new MethodReference.Transformer() { + @Override + public String transform(String input) { + return input.substring(1); + } + }; + assertThat(methodrefUse.transform(transform)).containsExactly("ergey", "arry", "lex"); + } + + @Test + public void testConstructorReference() { + ConstructorReference initRefUse = new ConstructorReference(ImmutableList.of("1", "2", "42")); + assertThat(initRefUse.toInt()).containsExactly(1, 2, 42); + } + + // Regression test for b/33304582 + @Test + public void testPrivateConstructorReference() { + ConstructorReference initRefUse = ConstructorReference.singleton().apply("17"); + assertThat(initRefUse.toInt()).containsExactly(17); + } + + // This test is similar to testPrivateConstructorReference but the private constructor of an inner + // class is used as a method reference. That causes Javac to generate a bridge constructor and + // a lambda body method that calls it, so the desugaring step doesn't need to do anything to make + // the private constructor visible. This is mostly to double-check that we don't interfere with + // this "already-working" scenario. + @Test + public void testPrivateConstructorAccessedThroughJavacGeneratedBridge() { + @SuppressWarnings("ReturnValueIgnored") + RuntimeException expected = + assertThrows( + RuntimeException.class, + () -> ConstructorReference.emptyThroughJavacGeneratedBridge().get()); + assertThat(expected).hasMessageThat().isEqualTo("got it!"); + } + + @Test + public void testExpressionMethodReference() { + assertThat( + MethodReference.stringChars(new StringBuilder().append("Larry").append("Sergey")) + .apply(5)) + .isEqualTo('S'); + } + + @Test + public void testFieldMethodReference() { + MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(methodrefUse.toPredicate().test("Larry")).isTrue(); + assertThat(methodrefUse.toPredicate().test("Sundar")).isFalse(); + } + + @Test + public void testConcreteFunctionWithInheritedBridgeMethods() { + assertThat(new ConcreteFunction().apply("1234567890987654321")).isEqualTo(1234567890987654321L); + assertThat(ConcreteFunction.parseAll(ImmutableList.of("5", "17"), new ConcreteFunction())) + .containsExactly(5L, 17L); + } + + @Test + public void testLambdaWithInheritedBridgeMethods() throws Exception { + assertThat(ConcreteFunction.toInt().apply("123456789")).isEqualTo(123456789); + assertThat(ConcreteFunction.parseAll(ImmutableList.of("5", "17"), ConcreteFunction.toInt())) + .containsExactly(5, 17); + // Expect String apply(Number) and any expected bridges + assertThat(ConcreteFunction.toInt().getClass().getDeclaredMethods()) + .hasLength(expectedBridgesFromSameTarget + 1); + // Check that we only copied over methods, no fields, from the functional interface + assertThrows( + NoSuchFieldException.class, + () -> + ConcreteFunction.toInt() + .getClass() + .getDeclaredField("DO_NOT_COPY_INTO_LAMBDA_CLASSES")); + assertThat(SpecializedFunction.class.getDeclaredField("DO_NOT_COPY_INTO_LAMBDA_CLASSES")) + .isNotNull(); + } + + /** Tests lambdas with bridge methods when the implemented interface is in a separate target. */ + @Test + public void testLambdaWithBridgeMethodsForInterfaceInSeparateTarget() { + assertThat(ConcreteFunction.isInt().test(123456789L)).isTrue(); + assertThat( + ConcreteFunction.doFilter( + ImmutableList.of(123456789L, 1234567890987654321L), ConcreteFunction.isInt())) + .containsExactly(123456789L); + // Expect test(Number) and any expected bridges + assertThat(ConcreteFunction.isInt().getClass().getDeclaredMethods()) + .hasLength(expectedBridgesFromSeparateTarget + 1); + } + + @Test + public void testLambdaInInterfaceStaticInitializer() { + assertThat(InterfaceWithLambda.DIGITS).containsExactly("0", "1").inOrder(); + // doesn't count but if there's a lambda method then Jacoco adds more methods + assertThat(InterfaceWithLambda.class.getDeclaredMethods().length != 0) + .isEqualTo(expectLambdaMethodsInInterfaces); + } + + /** Checks that the resource file included in the original Jar is still there unchanged. */ + @Test + public void testResourcePreserved() throws Exception { + try (InputStream content = Lambda.class.getResource("testresource.txt").openStream()) { + assertThat(new String(ByteStreams.toByteArray(content), UTF_8)).isEqualTo("test"); + } + } + + /** + * Test for b/62456849. After desugar, the method {@code lambda$mult$0} should still be in the + * class. + */ + @Test + public void testCallMethodWithLambdaNamingConvention() + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Method method = Lambda.class.getDeclaredMethod("lambda$mult$0"); + Object value = method.invoke(null); + assertThat(value).isInstanceOf(Integer.class); + assertThat((Integer) value).isEqualTo(0); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarJava8FunctionalTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarJava8FunctionalTest.java new file mode 100644 index 00000000000000..e288081b3bbd92 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarJava8FunctionalTest.java @@ -0,0 +1,426 @@ +// 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 static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.testdata.java8.AnnotationsOfDefaultMethodsShouldBeKept.AnnotatedInterface; +import com.google.devtools.build.android.desugar.testdata.java8.AnnotationsOfDefaultMethodsShouldBeKept.SomeAnnotation; +import com.google.devtools.build.android.desugar.testdata.java8.ConcreteDefaultInterfaceWithLambda; +import com.google.devtools.build.android.desugar.testdata.java8.ConcreteOverridesDefaultWithLambda; +import com.google.devtools.build.android.desugar.testdata.java8.DefaultInterfaceMethodWithStaticInitializer; +import com.google.devtools.build.android.desugar.testdata.java8.DefaultInterfaceWithBridges; +import com.google.devtools.build.android.desugar.testdata.java8.DefaultMethodFromSeparateJava8Target; +import com.google.devtools.build.android.desugar.testdata.java8.DefaultMethodFromSeparateJava8TargetOverridden; +import com.google.devtools.build.android.desugar.testdata.java8.DefaultMethodTransitivelyFromSeparateJava8Target; +import com.google.devtools.build.android.desugar.testdata.java8.FunctionWithDefaultMethod; +import com.google.devtools.build.android.desugar.testdata.java8.FunctionalInterfaceWithInitializerAndDefaultMethods; +import com.google.devtools.build.android.desugar.testdata.java8.GenericDefaultInterfaceWithLambda; +import com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod; +import com.google.devtools.build.android.desugar.testdata.java8.InterfaceWithDefaultMethod; +import com.google.devtools.build.android.desugar.testdata.java8.InterfaceWithDuplicateMethods.ClassWithDuplicateMethods; +import com.google.devtools.build.android.desugar.testdata.java8.InterfaceWithInheritedMethods; +import com.google.devtools.build.android.desugar.testdata.java8.Java7InterfaceWithBridges; +import com.google.devtools.build.android.desugar.testdata.java8.Named; +import com.google.devtools.build.android.desugar.testdata.java8.TwoInheritedDefaultMethods; +import com.google.devtools.build.android.desugar.testdata.java8.VisibilityTestClass; +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test that exercises classes in the {@code testdata_java8} package. This is meant to be run + * against a desugared version of those classes, which in turn exercise various desugaring features. + */ +@RunWith(JUnit4.class) +public class DesugarJava8FunctionalTest extends DesugarFunctionalTest { + + public DesugarJava8FunctionalTest() { + this(true, true); + } + + protected DesugarJava8FunctionalTest( + boolean expectBridgesFromSeparateTarget, boolean expectDefaultMethods) { + super(expectBridgesFromSeparateTarget, expectDefaultMethods); + } + + @Test + public void testLambdaInDefaultMethod() { + assertThat(new ConcreteDefaultInterfaceWithLambda().defaultWithLambda()) + .containsExactly("0", "1") + .inOrder(); + } + + @Test + public void testLambdaInDefaultCallsInterfaceMethod() { + assertThat(new ConcreteDefaultInterfaceWithLambda().defaultCallsInterfaceMethod()) + .containsExactly("1", "2") + .inOrder(); + } + + @Test + public void testOverrideLambdaInDefault() { + assertThat(new ConcreteOverridesDefaultWithLambda().defaultWithLambda()) + .containsExactly("2", "3") + .inOrder(); + } + + @Test + public void testLambdaInDefaultCallsOverrideMethod() { + assertThat(new ConcreteOverridesDefaultWithLambda().defaultCallsInterfaceMethod()) + .containsExactly("3", "4") + .inOrder(); + } + + @Test + public void testDefaultInterfaceMethodReference() { + InterfaceMethod methodrefUse = new InterfaceMethod.Concrete(); + List dest = + methodrefUse.defaultMethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(dest).containsExactly("Sergey"); + } + + @Test + public void testStaticInterfaceMethodReference() { + InterfaceMethod methodrefUse = new InterfaceMethod.Concrete(); + List dest = + methodrefUse.staticMethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(dest).containsExactly("Alex"); + } + + @Test + public void testLambdaCallsDefaultMethod() { + InterfaceMethod methodrefUse = new InterfaceMethod.Concrete(); + List dest = + methodrefUse.lambdaCallsDefaultMethod(ImmutableList.of("Sergey", "Larry", "Alex")); + assertThat(dest).containsExactly("Sergey"); + } + + @Test + public void testBootclasspathMethodInvocations() { + InterfaceMethod concrete = new InterfaceMethod.Concrete(); + assertThat(concrete.defaultInvokingBootclasspathMethods("Larry")).isEqualTo("Larry"); + } + + @Test + public void testStaticMethodsInInterface_explicitAndLambdaBody() { + List result = FunctionWithDefaultMethod.DoubleInts.add(ImmutableList.of(7, 39, 8), 3); + assertThat(result).containsExactly(10L, 42L, 11L).inOrder(); + } + + @Test + public void testOverriddenDefaultMethod_inHandwrittenClass() { + FunctionWithDefaultMethod doubler = new FunctionWithDefaultMethod.DoubleInts(); + assertThat(doubler.apply(7)).isEqualTo(14); + assertThat(doubler.twice(7)).isEqualTo(35); + } + + @Test + public void testOverriddenDefaultMethod_inHandwrittenSuperclass() { + FunctionWithDefaultMethod doubler = new FunctionWithDefaultMethod.DoubleInts2(); + assertThat(doubler.apply(7)).isEqualTo(14); + assertThat(doubler.twice(7)).isEqualTo(35); + } + + @Test + public void testInheritedDefaultMethod_inLambda() { + FunctionWithDefaultMethod doubler = + FunctionWithDefaultMethod.DoubleInts.doubleLambda(); + assertThat(doubler.apply(7)).isEqualTo(14); + assertThat(doubler.twice(7)).isEqualTo(28); + } + + @Test + public void testDefaultMethodReference_onLambda() { + FunctionWithDefaultMethod plus6 = FunctionWithDefaultMethod.DoubleInts.incTwice(3); + assertThat(plus6.apply(18)).isEqualTo(24); + assertThat(plus6.twice(18)).isEqualTo(30); + } + + @Test + public void testDefaultMethodReference_onHandwrittenClass() { + FunctionWithDefaultMethod times5 = FunctionWithDefaultMethod.DoubleInts.times5(); + assertThat(times5.apply(6)).isEqualTo(30); + assertThat(times5.twice(6)).isEqualTo(150); // Irrelevant that DoubleInts overrides twice() + } + + @Test + public void testStaticInterfaceMethodReferenceReturned() { + Function> factory = + FunctionWithDefaultMethod.DoubleInts.incFactory(); + assertThat(factory.apply(6).apply(7)).isEqualTo(13); + assertThat(factory.apply(6).twice(7)).isEqualTo(19); + } + + @Test + public void testSuperDefaultMethodInvocation() { + assertThat(new TwoInheritedDefaultMethods().name()).isEqualTo("One:Two"); + assertThat(new Named.DefaultName().name()).isEqualTo("DefaultName-once"); + assertThat(new Named.DefaultNameSubclass().name()).isEqualTo("DefaultNameSubclass-once-twice"); + } + + @Test + public void testInheritedPreferredOverDefault() throws Exception { + assertThat(new Named.ExplicitName("hello").name()).isEqualTo("hello"); + // Make sure AbstractName remains abstract, despite default method from implemented interface + assertThat(Modifier.isAbstract(Named.AbstractName.class.getMethod("name").getModifiers())) + .isTrue(); + } + + @Test + public void testRedefinedDefaultMethod() throws Exception { + assertThat(new InterfaceWithDefaultMethod.Version2().version()).isEqualTo(2); + } + + @Test + public void testDefaultMethodRedefinedInSubclass() throws Exception { + assertThat(new InterfaceWithDefaultMethod.AlsoVersion2().version()).isEqualTo(2); + } + + @Test + public void testDefaultMethodVisibility() { + assertThat(new VisibilityTestClass().m()).isEqualTo(42); + } + + /** Test for b/38302860 */ + @Test + public void testAnnotationsOfDefaultMethodsAreKept() throws Exception { + { + Annotation[] annotations = AnnotatedInterface.class.getAnnotations(); + assertThat(annotations).hasLength(1); + assertThat(annotations[0]).isInstanceOf(SomeAnnotation.class); + assertThat(((SomeAnnotation) annotations[0]).value()).isEqualTo(1); + } + { + Annotation[] annotations = + AnnotatedInterface.class.getMethod("annotatedAbstractMethod").getAnnotations(); + assertThat(annotations).hasLength(1); + assertThat(annotations[0]).isInstanceOf(SomeAnnotation.class); + assertThat(((SomeAnnotation) annotations[0]).value()).isEqualTo(2); + } + { + Annotation[] annotations = + AnnotatedInterface.class.getMethod("annotatedDefaultMethod").getAnnotations(); + assertThat(annotations).hasLength(1); + assertThat(annotations[0]).isInstanceOf(SomeAnnotation.class); + assertThat(((SomeAnnotation) annotations[0]).value()).isEqualTo(3); + } + } + /** Test for b/38308515 */ + @Test + public void testDefaultAndStaticMethodNameClash() { + final ClassWithDuplicateMethods instance = new ClassWithDuplicateMethods(); + assertThat(instance.getZero()).isEqualTo(0); + assertThat(instance.getZeroFromStaticInterfaceMethod()).isEqualTo(1); + } + + /** + * Test for b/38257037 + * + *

    Note that, we intentionally suppress unchecked warnings, because we expect some + * ClassCastException to test bridge methods. + */ + @SuppressWarnings("unchecked") + @Test + public void testBridgeAndDefaultMethods() { + { + DefaultInterfaceWithBridges object = new DefaultInterfaceWithBridges(); + Integer one = 1; + assertThat(object.copy(one)).isEqualTo(one); + assertThat(object.copy((Number) one)).isEqualTo(one); + assertThrows(ClassCastException.class, () -> object.copy(Double.valueOf(1))); + + assertThat(object.getNumber()).isInstanceOf(Double.class); + assertThat(object.getNumber()).isEqualTo(Double.valueOf(2.3d)); + assertThat(object.getDouble()).isEqualTo(Double.valueOf(2.3d)); + } + { + Java7InterfaceWithBridges.ClassAddTwo testObject = + new Java7InterfaceWithBridges.ClassAddTwo(); + assertThat(testObject.add(Integer.valueOf(2))).isEqualTo(4); + + @SuppressWarnings("rawtypes") + Java7InterfaceWithBridges top = testObject; + assertThat(top.add(Integer.valueOf(2))).isEqualTo(4); + assertThrows(ClassCastException.class, () -> top.add(new Object())); + assertThrows(ClassCastException.class, () -> top.add(Double.valueOf(1))); + assertThrows(ClassCastException.class, () -> top.add(Long.valueOf(1))); + + @SuppressWarnings("rawtypes") + Java7InterfaceWithBridges.LevelOne levelOne = testObject; + assertThat(levelOne.add(Integer.valueOf(2))).isEqualTo(4); + assertThrows(ClassCastException.class, () -> top.add(new Object())); + assertThrows(ClassCastException.class, () -> top.add(Double.valueOf(1))); + assertThrows(ClassCastException.class, () -> top.add(Long.valueOf(1))); + + @SuppressWarnings("rawtypes") + Java7InterfaceWithBridges.LevelOne levelTwo = testObject; + assertThat(levelTwo.add(Integer.valueOf(2))).isEqualTo(4); + assertThrows(ClassCastException.class, () -> levelTwo.add(Double.valueOf(1))); + assertThrows(ClassCastException.class, () -> levelTwo.add(Long.valueOf(1))); + } + { + GenericDefaultInterfaceWithLambda.ClassTwo testObject = + new GenericDefaultInterfaceWithLambda.ClassTwo(); + + assertThat(testObject.increment(Integer.valueOf(0))).isEqualTo(1); + assertThat(testObject.toString(Integer.valueOf(0))).isEqualTo("0"); + assertThat(testObject.getBaseValue()).isEqualTo(Integer.valueOf(0)); + + assertThat(testObject.toList(0)).isEmpty(); + assertThat(testObject.toList(1)).containsExactly(0).inOrder(); + assertThat(testObject.toList(2)).containsExactly(0, 1).inOrder(); + + assertThat(((Function>) testObject.toListSupplier()).apply(0)) + .isEmpty(); + assertThat(((Function>) testObject.toListSupplier()).apply(1)) + .containsExactly(0) + .inOrder(); + assertThat(((Function>) testObject.toListSupplier()).apply(2)) + .containsExactly(0, 1) + .inOrder(); + + assertThat(testObject.convertToStringList(ImmutableList.of(0))) + .containsExactly("0") + .inOrder(); + assertThat(testObject.convertToStringList(ImmutableList.of(0, 1))) + .containsExactly("0", "1") + .inOrder(); + + @SuppressWarnings("rawtypes") + GenericDefaultInterfaceWithLambda top = testObject; + assertThrows(ClassCastException.class, () -> top.increment(Long.valueOf(1))); + assertThrows(ClassCastException.class, () -> top.toString(Long.valueOf(1))); + assertThat(top.increment(Integer.valueOf(0))).isEqualTo(1); + assertThat(top.toString(Integer.valueOf(0))).isEqualTo("0"); + assertThat(top.getBaseValue()).isEqualTo(Integer.valueOf(0)); + + assertThat(top.toList(0)).isEmpty(); + assertThat(top.toList(1)).containsExactly(0).inOrder(); + assertThat(top.toList(2)).containsExactly(0, 1).inOrder(); + + assertThat(((Function>) top.toListSupplier()).apply(0)).isEmpty(); + assertThat(((Function>) top.toListSupplier()).apply(1)) + .containsExactly(0) + .inOrder(); + assertThat(((Function>) top.toListSupplier()).apply(2)) + .containsExactly(0, 1) + .inOrder(); + + assertThat(top.convertToStringList(ImmutableList.of(0))).containsExactly("0").inOrder(); + assertThat(top.convertToStringList(ImmutableList.of(0, 1))) + .containsExactly("0", "1") + .inOrder(); + } + { + @SuppressWarnings("rawtypes") + GenericDefaultInterfaceWithLambda testObject = + new GenericDefaultInterfaceWithLambda.ClassThree(); + assertThat(testObject.getBaseValue()).isEqualTo(Long.valueOf(0)); + assertThat(testObject.increment(Long.valueOf(0))).isEqualTo(Long.valueOf(0 + 1)); + assertThat(testObject.toString(Long.valueOf(0))).isEqualTo(Long.valueOf(0).toString()); + assertThrows(ClassCastException.class, () -> testObject.increment(Integer.valueOf(0))); + assertThrows(ClassCastException.class, () -> testObject.toString(Integer.valueOf(0))); + assertThat(testObject.toList(2)).containsExactly(Long.valueOf(0), Long.valueOf(1)).inOrder(); + assertThat(testObject.convertToStringList(testObject.toList(1))).containsExactly("0"); + assertThat(((Function>) testObject.toListSupplier()).apply(2)) + .containsExactly(Long.valueOf(0), Long.valueOf(1)); + } + } + + /** + * Test for b/62047432. + * + *

    When desugaring a functional interface with an executable clinit and default methods, we + * erase the body of clinit to avoid executing it during desugaring. This test makes sure that all + * the constants defined in the interface are still there after desugaring. + */ + @Test + public void testFunctionalInterfaceWithExecutableClinitStillWorkAfterDesugar() { + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.CONSTANT.length("").convert()) + .isEqualTo(0); + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.CONSTANT.length("1").convert()) + .isEqualTo(1); + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.BOOLEAN).isFalse(); + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.CHAR).isEqualTo('h'); + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.BYTE).isEqualTo(0); + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.SHORT).isEqualTo(0); + + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.INT).isEqualTo(0); + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.FLOAT).isEqualTo(0f); + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.LONG).isEqualTo(0); + assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.DOUBLE).isEqualTo(0d); + } + + /** Test for b/38255926. */ + @Test + public void testDefaultMethodInitializationOrder() { + { + assertThat(new DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetOne.C().sum()) + .isEqualTo(11); // To trigger loading the class C and its super interfaces. + assertThat( + DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetOne + .getRealInitializationOrder()) + .isEqualTo( + DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetOne + .getExpectedInitializationOrder()); + } + { + assertThat(new DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetTwo.C().sum()) + .isEqualTo(3); + assertThat( + DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetTwo + .getRealInitializationOrder()) + .isEqualTo( + DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetTwo + .getExpectedInitializationOrder()); + } + { + assertThat(new DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetThree.C().sum()) + .isEqualTo(11); + assertThat( + DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetThree + .getRealInitializationOrder()) + .isEqualTo( + DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetThree + .getExpectedInitializationOrder()); + } + } + + /** + * Tests that default methods on the classpath are correctly handled. We'll also verify the + * metadata that's emitted for this case to make sure the binary-wide double-check for correct + * desugaring of default and static interface methods keeps working (b/65645388). + */ + @Test + public void testDefaultMethodsInSeparateTarget() { + assertThat(new DefaultMethodFromSeparateJava8Target().dflt()).isEqualTo("dflt"); + assertThat(new DefaultMethodTransitivelyFromSeparateJava8Target().dflt()).isEqualTo("dflt"); + assertThat(new DefaultMethodFromSeparateJava8TargetOverridden().dflt()).isEqualTo("override"); + } + + /** Regression test for b/73355452 */ + @Test + public void testSuperCallToInheritedDefaultMethod() { + assertThat(new InterfaceWithInheritedMethods.Impl().name()).isEqualTo("Base"); + assertThat(new InterfaceWithInheritedMethods.Impl().suffix()).isEqualTo("!"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarJava8LikeAndroidStudioFunctionalTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarJava8LikeAndroidStudioFunctionalTest.java new file mode 100644 index 00000000000000..13edc579804ab1 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarJava8LikeAndroidStudioFunctionalTest.java @@ -0,0 +1,31 @@ +// 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 org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Variant of {@link DesugarJava8FunctionalTest} that doesn't expect any bridge methods already + * present on functional interfaces to be also present on generated classes, even where functional + * interfaces are defined in other compilations, which requires compiling against regular jar files + * instead of a classpath of -hjars. + */ +@RunWith(JUnit4.class) +public final class DesugarJava8LikeAndroidStudioFunctionalTest extends DesugarJava8FunctionalTest { + + public DesugarJava8LikeAndroidStudioFunctionalTest() { + super(/*expectBridgesFromSeparateTarget*/ false, /*expectDefaultMethods*/ true); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarLambdaTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarLambdaTest.java new file mode 100644 index 00000000000000..84cbecc5311003 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarLambdaTest.java @@ -0,0 +1,36 @@ +// 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 scenarios. */ +@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/src/test/java/com/google/devtools/build/android/desugar/DesugarLongCompareTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarLongCompareTest.java new file mode 100644 index 00000000000000..fef6757c1016d7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarLongCompareTest.java @@ -0,0 +1,135 @@ +// 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 static org.junit.Assert.fail; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; + +import com.google.devtools.build.android.desugar.testdata.ClassCallingLongCompare; +import org.objectweb.asm.Opcodes; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +/** The test case for the rewriter rewriting a call of Long.compare(long, long) to lcmp. */ +@RunWith(JUnit4.class) +public class DesugarLongCompareTest { + + @Test + public void testClassCallingLongCompareHasNoReferenceToLong_compare() { + try { + ClassReader reader = new ClassReader(ClassCallingLongCompare.class.getName()); + + AtomicInteger counter = new AtomicInteger(0); + + reader.accept( + new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + return new MethodVisitor(api) { + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == INVOKESTATIC + && owner.equals("java/lang/Long") + && name.equals("compare") + && desc.equals("(JJ)I")) { + counter.incrementAndGet(); + } + } + }; + } + }, + 0); + assertThat(counter.get()).isEqualTo(0); + } catch (IOException e) { + fail(); + } + } + + @Test + public void testCompareLongWithLambda() { + assertThat(ClassCallingLongCompare.compareLongWithLambda(1, 0)).isEqualTo(1); + assertThat(ClassCallingLongCompare.compareLongWithLambda(1, 1)).isEqualTo(0); + assertThat(ClassCallingLongCompare.compareLongWithLambda(1, 2)).isEqualTo(-1); + assertThat(ClassCallingLongCompare.compareLongWithLambda(Long.MAX_VALUE, Long.MIN_VALUE)) + .isEqualTo(1); + assertThat(ClassCallingLongCompare.compareLongWithLambda(Long.MAX_VALUE, Long.MAX_VALUE)) + .isEqualTo(0); + assertThat(ClassCallingLongCompare.compareLongWithLambda(Long.MIN_VALUE, Long.MAX_VALUE)) + .isEqualTo(-1); + } + + @Test + public void testCompareLongWithMethodReference() { + assertThat(ClassCallingLongCompare.compareLongWithMethodReference(1, 0)).isEqualTo(1); + assertThat(ClassCallingLongCompare.compareLongWithMethodReference(1, 1)).isEqualTo(0); + assertThat(ClassCallingLongCompare.compareLongWithMethodReference(1, 2)).isEqualTo(-1); + assertThat( + ClassCallingLongCompare.compareLongWithMethodReference(Long.MAX_VALUE, Long.MIN_VALUE)) + .isEqualTo(1); + assertThat( + ClassCallingLongCompare.compareLongWithMethodReference(Long.MAX_VALUE, Long.MAX_VALUE)) + .isEqualTo(0); + assertThat( + ClassCallingLongCompare.compareLongWithMethodReference(Long.MIN_VALUE, Long.MAX_VALUE)) + .isEqualTo(-1); + } + + @Test + public void testcompareLongByCallingLong_compare() { + assertThat(ClassCallingLongCompare.compareLongByCallingLong_compare(1, 0)).isEqualTo(1); + assertThat(ClassCallingLongCompare.compareLongByCallingLong_compare(1, 1)).isEqualTo(0); + assertThat(ClassCallingLongCompare.compareLongByCallingLong_compare(1, 2)).isEqualTo(-1); + assertThat( + ClassCallingLongCompare.compareLongByCallingLong_compare( + Long.MAX_VALUE, Long.MIN_VALUE)) + .isEqualTo(1); + assertThat( + ClassCallingLongCompare.compareLongByCallingLong_compare( + Long.MAX_VALUE, Long.MAX_VALUE)) + .isEqualTo(0); + assertThat( + ClassCallingLongCompare.compareLongByCallingLong_compare( + Long.MIN_VALUE, Long.MAX_VALUE)) + .isEqualTo(-1); + } + + @Test + public void testcompareLongByCallingLong_compare2() { + assertThat(ClassCallingLongCompare.compareLongByCallingLong_compare2(1, 0)).isEqualTo("g"); + assertThat(ClassCallingLongCompare.compareLongByCallingLong_compare2(1, 1)).isEqualTo("e"); + assertThat(ClassCallingLongCompare.compareLongByCallingLong_compare2(0, 1)).isEqualTo("l"); + + assertThat( + ClassCallingLongCompare.compareLongByCallingLong_compare2( + Long.MAX_VALUE, Long.MIN_VALUE)) + .isEqualTo("g"); + assertThat( + ClassCallingLongCompare.compareLongByCallingLong_compare2( + Long.MAX_VALUE, Long.MAX_VALUE)) + .isEqualTo("e"); + assertThat( + ClassCallingLongCompare.compareLongByCallingLong_compare2( + Long.MIN_VALUE, Long.MAX_VALUE)) + .isEqualTo("l"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarMainClassTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarMainClassTest.java new file mode 100644 index 00000000000000..3da06f4910f0bd --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarMainClassTest.java @@ -0,0 +1,70 @@ +// 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 static com.google.devtools.build.android.desugar.LambdaClassMaker.LAMBDA_METAFACTORY_DUMPER_PROPERTY; +import static org.junit.Assert.assertThrows; + +import com.google.common.base.Strings; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.Supplier; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link Desugar} */ +@RunWith(JUnit4.class) +public class DesugarMainClassTest { + + @Test + public void testVerifyLambdaDumpDirectoryRegistration() throws Exception { + if (Strings.isNullOrEmpty(System.getProperty(LAMBDA_METAFACTORY_DUMPER_PROPERTY))) { + testLambdaDumpDirSpecifiedInProgramFail(); + } else { + testLambdaDumpDirPassSpecifiedInCmdPass(); + } + } + + private void testLambdaDumpDirSpecifiedInProgramFail() throws Exception { + // This lambda will fail the dump directory registration, which is intended. + Supplier supplier = + () -> { + Path path = Paths.get(".").toAbsolutePath(); + System.setProperty(LAMBDA_METAFACTORY_DUMPER_PROPERTY, path.toString()); + return path; + }; + assertThrows( + NullPointerException.class, + () -> Desugar.verifyLambdaDumpDirectoryRegistered(supplier.get())); + } + + /** + * Test the LambdaMetafactory can be correctly set up by specifying the system property {@code + * LAMBDA_METAFACTORY_DUMPER_PROPERTY} in the command line. + */ + private void testLambdaDumpDirPassSpecifiedInCmdPass() throws IOException { + // The following lambda ensures that the LambdaMetafactory is loaded at the beginning of this + // test, so that the dump directory can be registered. + Path dumpDirectory = Desugar.createAndRegisterLambdaDumpDirectory(); + assertThat(dumpDirectory.toAbsolutePath().toString()) + .isEqualTo( + Paths.get(System.getProperty(LAMBDA_METAFACTORY_DUMPER_PROPERTY)) + .toAbsolutePath() + .toString()); + Desugar.verifyLambdaDumpDirectoryRegistered(dumpDirectory); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarObjectsRequireNonNullTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarObjectsRequireNonNullTest.java new file mode 100644 index 00000000000000..eb83166fdbc852 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarObjectsRequireNonNullTest.java @@ -0,0 +1,136 @@ +// 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 static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; + +import com.google.devtools.build.android.desugar.testdata.ClassCallingRequireNonNull; +import org.objectweb.asm.Opcodes; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +/** + * This test case tests the desugaring feature for Objects.requireNonNull. This feature replaces any + * call to this method with o.getClass() to check whether 'o' is null. + */ +@RunWith(JUnit4.class) +public class DesugarObjectsRequireNonNullTest { + + @Test + public void testClassCallingRequireNonNullHasNoReferenceToRequiresNonNull() { + try { + ClassReader reader = new ClassReader(ClassCallingRequireNonNull.class.getName()); + + AtomicInteger counterForSingleArgument = new AtomicInteger(0); + AtomicInteger counterForString = new AtomicInteger(0); + AtomicInteger counterForSupplier = new AtomicInteger(0); + + reader.accept( + new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + return new MethodVisitor(api) { + @Override + public void visitMethodInsn( + int opcode, String owner, String name, String desc, boolean itf) { + if (opcode == INVOKESTATIC + && owner.equals("java/util/Objects") + && name.equals("requireNonNull")) { + switch (desc) { + case "(Ljava/lang/Object;)Ljava/lang/Object;": + counterForSingleArgument.incrementAndGet(); + break; + case "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;": + counterForString.incrementAndGet(); + break; + case "(Ljava/lang/Object;Ljava/util/function/Supplier;)Ljava/lang/Object;": + counterForSupplier.incrementAndGet(); + break; + default: + fail("Unknown overloaded requireNonNull is found: " + desc); + } + } + } + }; + } + }, + 0); + assertThat(counterForSingleArgument.get()).isEqualTo(0); + // we do not desugar requireNonNull(Object, String) or requireNonNull(Object, Supplier) + assertThat(counterForString.get()).isEqualTo(1); + assertThat(counterForSupplier.get()).isEqualTo(1); + } catch (IOException e) { + fail(); + } + } + + @Test + public void testInliningImplicitCallToObjectsRequireNonNull() { + assertThrows( + NullPointerException.class, + () -> ClassCallingRequireNonNull.getStringLengthWithMethodReference(null)); + + assertThat(ClassCallingRequireNonNull.getStringLengthWithMethodReference("")).isEqualTo(0); + assertThat(ClassCallingRequireNonNull.getStringLengthWithMethodReference("1")).isEqualTo(1); + + assertThrows( + NullPointerException.class, + () -> + ClassCallingRequireNonNull.getStringLengthWithLambdaAndExplicitCallToRequireNonNull( + null)); + + assertThat( + ClassCallingRequireNonNull.getStringLengthWithLambdaAndExplicitCallToRequireNonNull("")) + .isEqualTo(0); + assertThat( + ClassCallingRequireNonNull.getStringLengthWithLambdaAndExplicitCallToRequireNonNull( + "1")) + .isEqualTo(1); + } + + @Test + public void testInliningExplicitCallToObjectsRequireNonNull() { + assertThrows( + NullPointerException.class, () -> ClassCallingRequireNonNull.getFirstCharVersionOne(null)); + + assertThrows( + NullPointerException.class, () -> ClassCallingRequireNonNull.getFirstCharVersionTwo(null)); + + assertThrows( + NullPointerException.class, + () -> ClassCallingRequireNonNull.callRequireNonNullWithArgumentString(null)); + + assertThrows( + NullPointerException.class, + () -> ClassCallingRequireNonNull.callRequireNonNullWithArgumentSupplier(null)); + + assertThat(ClassCallingRequireNonNull.getFirstCharVersionOne("hello")).isEqualTo('h'); + assertThat(ClassCallingRequireNonNull.getFirstCharVersionTwo("hello")).isEqualTo('h'); + + assertThat(ClassCallingRequireNonNull.callRequireNonNullWithArgumentString("hello")) + .isEqualTo('h'); + assertThat(ClassCallingRequireNonNull.callRequireNonNullWithArgumentSupplier("hello")) + .isEqualTo('h'); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/DesugarTryWithResourcesFunctionalTest.java b/src/test/java/com/google/devtools/build/android/desugar/DesugarTryWithResourcesFunctionalTest.java new file mode 100644 index 00000000000000..41eb2e0ac18346 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/DesugarTryWithResourcesFunctionalTest.java @@ -0,0 +1,152 @@ +// 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 static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.getStrategyClassName; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.getTwrStrategyClassNameSpecifiedInSystemProperty; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isMimicStrategy; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isNullStrategy; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isReuseStrategy; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +import com.google.devtools.build.android.desugar.runtime.ThrowableExtension; +import com.google.devtools.build.android.desugar.testdata.ClassUsingTryWithResources; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** The functional test for desugaring try-with-resources. */ +@RunWith(JUnit4.class) +public class DesugarTryWithResourcesFunctionalTest { + + @Test + public void testCheckSuppressedExceptionsReturningEmptySuppressedExceptions() { + Throwable[] suppressed = ClassUsingTryWithResources.checkSuppressedExceptions(false); + assertThat(suppressed).isEmpty(); + } + + @Test + public void testPrintStackTraceOfCaughtException() { + String trace = ClassUsingTryWithResources.printStackTraceOfCaughtException(); + if (isMimicStrategy()) { + assertThat(trace.toLowerCase()).contains("suppressed"); + } else if (isReuseStrategy()) { + assertThat(trace.toLowerCase()).contains("suppressed"); + } else if (isNullStrategy()) { + assertThat(trace.toLowerCase()).doesNotContain("suppressed"); + } else { + fail("unexpected desugaring strategy " + ThrowableExtension.getStrategy()); + } + } + + @Test + public void testCheckSuppressedExceptionReturningOneSuppressedException() { + Throwable[] suppressed = ClassUsingTryWithResources.checkSuppressedExceptions(true); + + if (isMimicStrategy()) { + assertThat(suppressed).hasLength(1); + } else if (isReuseStrategy()) { + assertThat(suppressed).hasLength(1); + } else if (isNullStrategy()) { + assertThat(suppressed).isEmpty(); + } else { + fail("unexpected desugaring strategy " + ThrowableExtension.getStrategy()); + } + } + + @Test + public void testSimpleTryWithResources() { + + Exception expected = + assertThrows(Exception.class, () -> ClassUsingTryWithResources.simpleTryWithResources()); + assertThat(expected.getClass()).isEqualTo(RuntimeException.class); + + String expectedStrategyName = getTwrStrategyClassNameSpecifiedInSystemProperty(); + assertThat(getStrategyClassName()).isEqualTo(expectedStrategyName); + if (isMimicStrategy()) { + assertThat(expected.getSuppressed()).isEmpty(); + assertThat(ThrowableExtension.getSuppressed(expected)).hasLength(1); + assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass()) + .isEqualTo(IOException.class); + } else if (isReuseStrategy()) { + assertThat(expected.getSuppressed()).hasLength(1); + assertThat(expected.getSuppressed()[0].getClass()).isEqualTo(IOException.class); + assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass()) + .isEqualTo(IOException.class); + } else if (isNullStrategy()) { + assertThat(expected.getSuppressed()).isEmpty(); + assertThat(ThrowableExtension.getSuppressed(expected)).isEmpty(); + } else { + fail("unexpected desugaring strategy " + getStrategyClassName()); + } + } + + @Test + public void simpleTryWithResources_multipleClosableResources() { + + IOException expected = + assertThrows( + IOException.class, () -> ClassUsingTryWithResources.multipleTryWithResources()); + + String expectedStrategyName = getTwrStrategyClassNameSpecifiedInSystemProperty(); + assertThat(getStrategyClassName()).isEqualTo(expectedStrategyName); + if (isMimicStrategy()) { + assertThat(expected.getSuppressed()).isEmpty(); + assertThat(ThrowableExtension.getSuppressed(expected)).hasLength(2); + assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass()) + .isEqualTo(IOException.class); + } else if (isReuseStrategy()) { + assertThat(expected.getSuppressed()).hasLength(2); + assertThat(expected.getSuppressed()[0].getClass()).isEqualTo(IOException.class); + assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass()) + .isEqualTo(IOException.class); + } else if (isNullStrategy()) { + assertThat(expected.getSuppressed()).isEmpty(); + assertThat(ThrowableExtension.getSuppressed(expected)).isEmpty(); + } else { + fail("unexpected desugaring strategy " + getStrategyClassName()); + } + } + + @Test + public void testInheritanceTryWithResources() { + + Exception expected = + assertThrows( + Exception.class, () -> ClassUsingTryWithResources.inheritanceTryWithResources()); + assertThat(expected.getClass()).isEqualTo(RuntimeException.class); + + String expectedStrategyName = getTwrStrategyClassNameSpecifiedInSystemProperty(); + assertThat(getStrategyClassName()).isEqualTo(expectedStrategyName); + if (isMimicStrategy()) { + assertThat(expected.getSuppressed()).isEmpty(); + assertThat(ThrowableExtension.getSuppressed(expected)).hasLength(1); + assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass()) + .isEqualTo(IOException.class); + } else if (isReuseStrategy()) { + assertThat(expected.getSuppressed()).hasLength(1); + assertThat(expected.getSuppressed()[0].getClass()).isEqualTo(IOException.class); + assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass()) + .isEqualTo(IOException.class); + } else if (isNullStrategy()) { + assertThat(expected.getSuppressed()).isEmpty(); + assertThat(ThrowableExtension.getSuppressed(expected)).isEmpty(); + } else { + fail("unexpected desugaring strategy " + getStrategyClassName()); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java b/src/test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java new file mode 100644 index 00000000000000..528c78e6241138 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/FrameInfoTest.java @@ -0,0 +1,51 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.android.desugar; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.BytecodeTypeInference.FrameInfo; +import com.google.devtools.build.android.desugar.BytecodeTypeInference.InferredType; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link BytecodeTypeInference.FrameInfo} */ +@RunWith(JUnit4.class) +public class FrameInfoTest { + + @Test + public void testFieldsAreSetCorrectly() { + { + FrameInfo info = FrameInfo.create(ImmutableList.of(), ImmutableList.of()); + assertThat(info.locals()).isEmpty(); + assertThat(info.stack()).isEmpty(); + } + { + FrameInfo info = + FrameInfo.create(ImmutableList.of(InferredType.INT), ImmutableList.of(InferredType.BYTE)); + assertThat(info.locals()).containsExactly(InferredType.INT).inOrder(); + assertThat(info.stack()).containsExactly(InferredType.BYTE).inOrder(); + } + { + FrameInfo info = + FrameInfo.create( + ImmutableList.of(InferredType.INT, InferredType.BYTE), + ImmutableList.of(InferredType.BOOLEAN, InferredType.LONG)); + assertThat(info.locals()).containsExactly(InferredType.INT, InferredType.BYTE).inOrder(); + assertThat(info.stack()).containsExactly(InferredType.BOOLEAN, InferredType.LONG).inOrder(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/Java7CompatibilityTest.java b/src/test/java/com/google/devtools/build/android/desugar/Java7CompatibilityTest.java new file mode 100644 index 00000000000000..a8e8b9e2e28165 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/Java7CompatibilityTest.java @@ -0,0 +1,130 @@ +// 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 static org.junit.Assert.assertThrows; + +import com.google.devtools.build.android.desugar.io.BitFlags; +import org.objectweb.asm.Opcodes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +@RunWith(JUnit4.class) +public class Java7CompatibilityTest { + + @Test + public void testJava7CompatibleInterface() throws Exception { + ClassReader reader = new ClassReader(ExtendsDefault.class.getName()); + ClassTester tester = new ClassTester(); + reader.accept(new Java7Compatibility(tester, null, null), 0); + assertThat(tester.version).isEqualTo(Opcodes.V1_7); + assertThat(tester.bridgeMethods).isEqualTo(0); // make sure we strip bridge methods + assertThat(tester.clinitMethods).isEqualTo(1); // make sure we don't strip + } + + @Test + public void testDefaultMethodFails() throws Exception { + ClassReader reader = new ClassReader(WithDefault.class.getName()); + IllegalArgumentException expected = + assertThrows( + IllegalArgumentException.class, + () -> reader.accept(new Java7Compatibility(null, null, null), 0)); + assertThat(expected).hasMessageThat().contains("getVersion()I"); + } + + /** + * Tests that a class implementing interfaces with bridge methods redeclares those bridges. This + * is behavior of javac that we rely on. + */ + @Test + public void testConcreteClassRedeclaresBridges() throws Exception { + ClassReader reader = new ClassReader(Impl.class.getName()); + ClassTester tester = new ClassTester(); + reader.accept(new Java7Compatibility(tester, null, null), 0); + assertThat(tester.version).isEqualTo(Opcodes.V1_7); + assertThat(tester.bridgeMethods).isEqualTo(2); + } + + private static class ClassTester extends ClassVisitor { + + int version; + int bridgeMethods; + int clinitMethods; + + private ClassTester() { + super(Opcodes.ASM9, null); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + this.version = version; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + if (BitFlags.isSet(access, Opcodes.ACC_BRIDGE)) { + ++bridgeMethods; + } + if ("".equals(name)) { + ++clinitMethods; + } + return super.visitMethod(access, name, desc, signature, exceptions); + } + } + + interface WithDefault { + default int getVersion() { + return 18; + } + + T get(); + } + + // Javac will generate a default bridge method "Object get()" that Java7Compatibility will remove + interface ExtendsDefault extends WithDefault { + public static final Integer X = Integer.valueOf(37); + + String name(); + + @Override + T get(); + } + + // Javac will generate 2 bridge methods that we *don't* want to remove + static class Impl implements ExtendsDefault { + @Override + public Integer get() { + return X; + } + + @Override + public String name() { + return "test"; + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/MethodInfoTest.java b/src/test/java/com/google/devtools/build/android/desugar/MethodInfoTest.java new file mode 100644 index 00000000000000..ebe9ba6d247047 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/MethodInfoTest.java @@ -0,0 +1,33 @@ +// 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 org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link MethodInfo} */ +@RunWith(JUnit4.class) +public class MethodInfoTest { + + @Test + public void testMethodInfoAreCorrectlySet() { + MethodInfo method = MethodInfo.create("owner", "name", "desc"); + assertThat(method.owner()).isEqualTo("owner"); + assertThat(method.name()).isEqualTo("name"); + assertThat(method.desc()).isEqualTo("desc"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/StackMapBugTest.java b/src/test/java/com/google/devtools/build/android/desugar/StackMapBugTest.java new file mode 100644 index 00000000000000..26aa52821bfa67 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/StackMapBugTest.java @@ -0,0 +1,45 @@ +// 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 org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import test.util.TestClassForStackMapFrame; + +/** This test case is for testing the fix for b/36654936. */ +@RunWith(JUnit4.class) +public class StackMapBugTest { + + /** This is a regression test for b/36654936 (external ASM bug 317785) */ + @Test + public void testAsmBug317785() { + int result = TestClassForStackMapFrame.testInputForAsmBug317785(); + assertThat(result).isEqualTo(20); + } + + /** + * This is a regression test for b/36654936 (external ASM bug 317785). The first attempted fix + * cl/152199391 caused stack map frame corruption, which caused the following test to fail. + */ + @Test + public void testStackMapFrameCorrectness() { + TestClassForStackMapFrame testObject = new TestClassForStackMapFrame(); + assertThat(testObject.joinIntegers(0)).isEmpty(); + assertThat(testObject.joinIntegers(1)).isEqualTo("0=Even"); + assertThat(testObject.joinIntegers(2)).isEqualTo("0=Even,1=Odd"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java b/src/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java new file mode 100644 index 00000000000000..c2579bbb40875d --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/TryWithResourcesRewriterTest.java @@ -0,0 +1,431 @@ +// 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 static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.getStrategyClassName; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.getTwrStrategyClassNameSpecifiedInSystemProperty; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isMimicStrategy; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isNullStrategy; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isReuseStrategy; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; +import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; + +import com.google.devtools.build.android.desugar.io.BitFlags; +import com.google.devtools.build.android.desugar.runtime.ThrowableExtension; +import com.google.devtools.build.android.desugar.testdata.ClassUsingTryWithResources; +import org.objectweb.asm.Opcodes; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/** This is the unit test for {@link TryWithResourcesRewriter} */ +@RunWith(JUnit4.class) +public class TryWithResourcesRewriterTest { + + private final DesugaringClassLoader classLoader = + new DesugaringClassLoader(ClassUsingTryWithResources.class.getName()); + private Class desugaredClass; + + @Before + public void setup() { + try { + desugaredClass = classLoader.findClass(ClassUsingTryWithResources.class.getName()); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + + @Test + public void testMethodsAreDesugared() { + // verify whether the desugared class is indeed desugared. + DesugaredThrowableMethodCallCounter origCounter = + countDesugaredThrowableMethodCalls(ClassUsingTryWithResources.class); + DesugaredThrowableMethodCallCounter desugaredCounter = + countDesugaredThrowableMethodCalls(classLoader.classContent, classLoader); + /** + * In java9, javac creates a helper method {@code $closeResource(Throwable, AutoCloseable) + * to close resources. So, the following number 3 is highly dependant on the version of javac. + */ + assertThat(hasAutoCloseable(classLoader.classContent)).isFalse(); + assertThat(classLoader.numOfTryWithResourcesInvoked.intValue()).isAtLeast(2); + assertThat(classLoader.visitedExceptionTypes) + .containsExactly( + "java/lang/Exception", "java/lang/Throwable", "java/io/UnsupportedEncodingException"); + assertDesugaringBehavior(origCounter, desugaredCounter); + } + + @Test + public void testCheckSuppressedExceptionsReturningEmptySuppressedExceptions() { + { + Throwable[] suppressed = ClassUsingTryWithResources.checkSuppressedExceptions(false); + assertThat(suppressed).isEmpty(); + } + try { + Throwable[] suppressed = + (Throwable[]) + desugaredClass + .getMethod("checkSuppressedExceptions", boolean.class) + .invoke(null, Boolean.FALSE); + assertThat(suppressed).isEmpty(); + } catch (Exception e) { + e.printStackTrace(); + throw new AssertionError(e); + } + } + + @Test + public void testPrintStackTraceOfCaughtException() { + { + String trace = ClassUsingTryWithResources.printStackTraceOfCaughtException(); + assertThat(trace.toLowerCase()).contains("suppressed"); + } + try { + String trace = + (String) desugaredClass.getMethod("printStackTraceOfCaughtException").invoke(null); + + if (isMimicStrategy()) { + assertThat(trace.toLowerCase()).contains("suppressed"); + } else if (isReuseStrategy()) { + assertThat(trace.toLowerCase()).contains("suppressed"); + } else if (isNullStrategy()) { + assertThat(trace.toLowerCase()).doesNotContain("suppressed"); + } else { + fail("unexpected desugaring strategy " + ThrowableExtension.getStrategy()); + } + } catch (Exception e) { + e.printStackTrace(); + throw new AssertionError(e); + } + } + + @Test + public void testCheckSuppressedExceptionReturningOneSuppressedException() { + { + Throwable[] suppressed = ClassUsingTryWithResources.checkSuppressedExceptions(true); + assertThat(suppressed).hasLength(1); + } + try { + Throwable[] suppressed = + (Throwable[]) + desugaredClass + .getMethod("checkSuppressedExceptions", boolean.class) + .invoke(null, Boolean.TRUE); + + if (isMimicStrategy()) { + assertThat(suppressed).hasLength(1); + } else if (isReuseStrategy()) { + assertThat(suppressed).hasLength(1); + } else if (isNullStrategy()) { + assertThat(suppressed).isEmpty(); + } else { + fail("unexpected desugaring strategy " + ThrowableExtension.getStrategy()); + } + } catch (Exception e) { + e.printStackTrace(); + throw new AssertionError(e); + } + } + + @Test + public void testSimpleTryWithResources() throws Throwable { + { + RuntimeException expected = + assertThrows( + RuntimeException.class, () -> ClassUsingTryWithResources.simpleTryWithResources()); + assertThat(expected.getClass()).isEqualTo(RuntimeException.class); + assertThat(expected.getSuppressed()).hasLength(1); + assertThat(expected.getSuppressed()[0].getClass()).isEqualTo(IOException.class); + } + + try { + InvocationTargetException e = + assertThrows( + InvocationTargetException.class, + () -> desugaredClass.getMethod("simpleTryWithResources").invoke(null)); + throw e.getCause(); + } catch (RuntimeException expected) { + String expectedStrategyName = getTwrStrategyClassNameSpecifiedInSystemProperty(); + assertThat(getStrategyClassName()).isEqualTo(expectedStrategyName); + if (isMimicStrategy()) { + assertThat(expected.getSuppressed()).isEmpty(); + assertThat(ThrowableExtension.getSuppressed(expected)).hasLength(1); + assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass()) + .isEqualTo(IOException.class); + } else if (isReuseStrategy()) { + assertThat(expected.getSuppressed()).hasLength(1); + assertThat(expected.getSuppressed()[0].getClass()).isEqualTo(IOException.class); + assertThat(ThrowableExtension.getSuppressed(expected)[0].getClass()) + .isEqualTo(IOException.class); + } else if (isNullStrategy()) { + assertThat(expected.getSuppressed()).isEmpty(); + assertThat(ThrowableExtension.getSuppressed(expected)).isEmpty(); + } else { + fail("unexpected desugaring strategy " + ThrowableExtension.getStrategy()); + } + } + } + + private static void assertDesugaringBehavior( + DesugaredThrowableMethodCallCounter orig, DesugaredThrowableMethodCallCounter desugared) { + assertThat(desugared.countThrowableGetSuppressed()).isEqualTo(orig.countExtGetSuppressed()); + assertThat(desugared.countThrowableAddSuppressed()).isEqualTo(orig.countExtAddSuppressed()); + assertThat(desugared.countThrowablePrintStackTrace()).isEqualTo(orig.countExtPrintStackTrace()); + assertThat(desugared.countThrowablePrintStackTracePrintStream()) + .isEqualTo(orig.countExtPrintStackTracePrintStream()); + assertThat(desugared.countThrowablePrintStackTracePrintWriter()) + .isEqualTo(orig.countExtPrintStackTracePrintWriter()); + + assertThat(orig.countThrowableGetSuppressed()).isEqualTo(desugared.countExtGetSuppressed()); + // $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); + assertThat(desugared.countThrowableAddSuppressed()).isEqualTo(0); + assertThat(desugared.countThrowableGetSuppressed()).isEqualTo(0); + } + + private static DesugaredThrowableMethodCallCounter countDesugaredThrowableMethodCalls( + Class klass) { + try { + ClassReader reader = new ClassReader(klass.getName()); + DesugaredThrowableMethodCallCounter counter = + new DesugaredThrowableMethodCallCounter(klass.getClassLoader()); + reader.accept(counter, 0); + return counter; + } catch (IOException e) { + e.printStackTrace(); + fail(e.toString()); + return null; + } + } + + private static DesugaredThrowableMethodCallCounter countDesugaredThrowableMethodCalls( + byte[] content, ClassLoader loader) { + ClassReader reader = new ClassReader(content); + DesugaredThrowableMethodCallCounter counter = new DesugaredThrowableMethodCallCounter(loader); + reader.accept(counter, 0); + return counter; + } + + /** Check whether java.lang.AutoCloseable is used as arguments of any method. */ + private static boolean hasAutoCloseable(byte[] classContent) { + ClassReader reader = new ClassReader(classContent); + final AtomicInteger counter = new AtomicInteger(); + ClassVisitor visitor = + new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + for (Type argumentType : Type.getArgumentTypes(desc)) { + if ("Ljava/lang/AutoCloseable;".equals(argumentType.getDescriptor())) { + counter.incrementAndGet(); + } + } + return null; + } + }; + reader.accept(visitor, 0); + return counter.get() > 0; + } + + private static class DesugaredThrowableMethodCallCounter extends ClassVisitor { + private final ClassLoader classLoader; + private final Map counterMap; + private int syntheticCloseResourceCount; + + public DesugaredThrowableMethodCallCounter(ClassLoader loader) { + super(Opcodes.ASM9); + classLoader = loader; + counterMap = new HashMap<>(); + TryWithResourcesRewriter.TARGET_METHODS + .entries() + .forEach(entry -> counterMap.put(entry.getKey() + entry.getValue(), new AtomicInteger())); + TryWithResourcesRewriter.TARGET_METHODS + .entries() + .forEach( + entry -> + counterMap.put( + entry.getKey() + + TryWithResourcesRewriter.METHOD_DESC_MAP.get(entry.getValue()), + new AtomicInteger())); + } + + @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(); + } + + private class InvokeCounter extends MethodVisitor { + + public InvokeCounter() { + super(Opcodes.ASM9); + } + + private boolean isAssignableToThrowable(String owner) { + try { + Class ownerClass = classLoader.loadClass(owner.replace('/', '.')); + return Throwable.class.isAssignableFrom(ownerClass); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + String signature = name + desc; + if ((opcode == INVOKEVIRTUAL && isAssignableToThrowable(owner)) + || (opcode == INVOKESTATIC + && Type.getInternalName(ThrowableExtension.class).equals(owner))) { + AtomicInteger counter = counterMap.get(signature); + if (counter == null) { + return; + } + counter.incrementAndGet(); + } + } + } + + public int getSyntheticCloseResourceCount() { + return syntheticCloseResourceCount; + } + + public int countThrowableAddSuppressed() { + return counterMap.get("addSuppressed(Ljava/lang/Throwable;)V").get(); + } + + public int countThrowableGetSuppressed() { + return counterMap.get("getSuppressed()[Ljava/lang/Throwable;").get(); + } + + public int countThrowablePrintStackTrace() { + return counterMap.get("printStackTrace()V").get(); + } + + public int countThrowablePrintStackTracePrintStream() { + return counterMap.get("printStackTrace(Ljava/io/PrintStream;)V").get(); + } + + public int countThrowablePrintStackTracePrintWriter() { + return counterMap.get("printStackTrace(Ljava/io/PrintWriter;)V").get(); + } + + public int countExtAddSuppressed() { + return counterMap.get("addSuppressed(Ljava/lang/Throwable;Ljava/lang/Throwable;)V").get(); + } + + public int countExtGetSuppressed() { + return counterMap.get("getSuppressed(Ljava/lang/Throwable;)[Ljava/lang/Throwable;").get(); + } + + public int countExtPrintStackTrace() { + return counterMap.get("printStackTrace(Ljava/lang/Throwable;)V").get(); + } + + public int countExtPrintStackTracePrintStream() { + return counterMap.get("printStackTrace(Ljava/lang/Throwable;Ljava/io/PrintStream;)V").get(); + } + + public int countExtPrintStackTracePrintWriter() { + return counterMap.get("printStackTrace(Ljava/lang/Throwable;Ljava/io/PrintWriter;)V").get(); + } + } + + private static class DesugaringClassLoader extends ClassLoader { + + private final String targetedClassName; + private Class klass; + private byte[] classContent; + private final Set visitedExceptionTypes = new HashSet<>(); + private final AtomicInteger numOfTryWithResourcesInvoked = new AtomicInteger(); + + public DesugaringClassLoader(String targetedClassName) { + super(DesugaringClassLoader.class.getClassLoader()); + this.targetedClassName = targetedClassName; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (name.equals(targetedClassName)) { + if (klass != null) { + return klass; + } + // desugar the class, and return the desugared one. + classContent = desugarTryWithResources(name); + klass = defineClass(name, classContent, 0, classContent.length); + return klass; + } else { + return super.findClass(name); + } + } + + 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, + scanner.hasCloseResourceMethod()); + reader.accept(rewriter, 0); + return writer.toByteArray(); + } catch (IOException e) { + fail(e.toString()); + return null; // suppress compiler error. + } + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/b72690624_testdata.jar b/src/test/java/com/google/devtools/build/android/desugar/b72690624_testdata.jar new file mode 100644 index 0000000000000000000000000000000000000000..6cca3a0f72d6bdd647696d1a7ab775c2368a3d81 GIT binary patch literal 3088 zcmbuBTTk3Z6vxl(&AZ01yEG6s-07y>3wwd4SE@<@L9$|@s30m;Y9q7j5QoJ^wxOjj z{Rn-j@(tRjzVsoYN~JzmQB|qWeQeb)Q7P*G*swON6H*HD&Uog`Ii8u{{LgHxMq@Hj zk|_7pa+3moDT>kB^49H2V|}Ie>?zU4InzJ?`0n0anDcWDPuFzq_IhJwd2_3}wz8%g z`hCrID%+Z6>!$JE)LgaRFE3e|>S*oC;xRY8Q?1{do13rfn&xgts~o*ORk-6;ry549 zTI`qmWk2#?pPm26I3hcUQ1eYrK3#5p^X1;VwFnVE!(9{A(hO&*quO@0#lKIVJ^2}( zw`ej&QOe1f@C}JZB^r}xT#(!_49#-w1u6cEW=k|7Xuxe*RBi3lB(cA(b`CW`q9G{p zzHaEw2ZEx7;a8(kLQ)#@%4^l59&09U0^YZ;2!}1RtEc?LJQN#q%x&MtLTrS zOqa?aj3F(}KBlNgYCk##+-?~|`%qV0s6FJ;ls-i+4Q3@b6f0*0C}vU02oa+k7!RQK z8&I41>7UuJacKCNhG95rVZ!!S3X>XU&PQcTYfPqbnviHxqN$e#X($AwD?{^qO%*i$ z9AMmu0T!jB?(ekKyrF&0_z&?iR0D#R_f+#TkR(cij78IQY{ya$)->mV*|yUOn#s_s zDDn!<^J@w=@O6m_3KgmJe^sw1S;$nTSuZ!R22@bRNf1;BTkEh>E9k$R?;hM=E{#Z3 zRUpXo3b2(0&G>Sp8%O4zR%^N&f)!Nnt1ZX0js^AY`)?B60%c9p+&erF^lKq7ky8iW z;@?$`c1N@Gp5;QoY}2uHV>j4xZs6AOfp)b{FXW5I76Tu08g@zTbT)NITaf5YK{tCA zBy2u{CSGg@?TO=XLTR8mpc6GtVkuV;I72Q$Qn)4SX->KPgkMk2REK5BQ=JSPWuYH~ zNT+9cGK)T_OGf5Rgu<}Hd5mXSOO`V7j}-gZQ!n=8<=JdtJm**bq^Nt#W3A#Gd;{-s zh)Eir5-6up*69tJgMM)u=EBBRKmByJf;Y@YlUadUwwALoePs{c&Yf zyF&wa&|vVe+oSK{TU0%14Sa9dZPFj|OLs`%Ue)5eWiV{mb;0*hMmh;s^lql~E)PMM fVQIm4`3RXT++`q1Y^=r^I&5Ep9)X57Hi+n754OZ< literal 0 HcmV?d00001 diff --git a/src/test/java/com/google/devtools/build/android/desugar/capture_lambda_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/capture_lambda_disassembled_golden.txt new file mode 100644 index 00000000000000..1f4905a12c1101 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/capture_lambda_disassembled_golden.txt @@ -0,0 +1,22 @@ +Compiled from "CaptureLambda.java" +final class com.google.devtools.build.android.desugar.testdata.CaptureLambda$$Lambda$0 implements java.util.function.Predicate { + private final java.lang.String arg$1; + + com.google.devtools.build.android.desugar.testdata.CaptureLambda$$Lambda$0(java.lang.String); + Code: + 0: aload_0 + 1: invokespecial #14 // Method java/lang/Object."":()V + 4: aload_0 + 5: aload_1 + 6: putfield #16 // Field arg$1:Ljava/lang/String; + 9: return + + public boolean test(java.lang.Object); + Code: + 0: aload_0 + 1: getfield #16 // Field arg$1:Ljava/lang/String; + 4: aload_1 + 5: checkcast #20 // class java/lang/String + 8: invokestatic #26 // Method com/google/devtools/build/android/desugar/testdata/CaptureLambda.lambda$prefixed$0$CaptureLambda:(Ljava/lang/String;Ljava/lang/String;)Z + 11: ireturn +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/class_with_inherited_abstract_method_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/class_with_inherited_abstract_method_disassembled_golden.txt new file mode 100644 index 00000000000000..9ab9eb720d4977 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/class_with_inherited_abstract_method_disassembled_golden.txt @@ -0,0 +1,4 @@ +Compiled from "Named.java" +public abstract class com.google.devtools.build.android.desugar.testdata.java8.Named$AbstractName extends com.google.devtools.build.android.desugar.testdata.java8.Named$AbstractNameBase implements com.google.devtools.build.android.desugar.testdata.java8.Named { + public com.google.devtools.build.android.desugar.testdata.java8.Named$AbstractName(); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/class_with_lambdas_in_implemented_interface_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/class_with_lambdas_in_implemented_interface_disassembled_golden.txt new file mode 100644 index 00000000000000..a3fe904e2d02db --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/class_with_lambdas_in_implemented_interface_disassembled_golden.txt @@ -0,0 +1,11 @@ +Compiled from "InterfaceMethod.java" +public class com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod$Concrete implements com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod { + private final java.lang.Object o; + public com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod$Concrete(); + public java.util.List defaultMethodReference(java.util.List); + public java.lang.String defaultInvokingBootclasspathMethods(java.lang.String); + public java.util.List staticMethodReference(java.util.List); + public java.util.List lambdaCallsDefaultMethod(java.util.List); + public boolean startsWithS(java.lang.String); + public boolean startsWithS(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.lang.String); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/BUILD b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/BUILD new file mode 100644 index 00000000000000..23dc9beb3da96c --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/BUILD @@ -0,0 +1,21 @@ +load("@rules_java//java:defs.bzl", "java_import") + +# Description: +# Tests for the extension classes for desugaring try-with-resources for Android. +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["**"]), +) + +java_import( + name = "test_subjects", + jars = ["test_subjects.jar"], +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh new file mode 100755 index 00000000000000..11666208628e68 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/generate_jar.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# Copyright 2016 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# I intentionally create this script to create a checked-in jar, because the test cases for +# byte code type inference uses golden files, which consequently relies on the version of javac +# compilers. So instead of creating jar files at build time, we check in a jar file. +# + +javac testsubjects/TestSubject.java + +jar cf test_subjects.jar testsubjects/TestSubject.class \ No newline at end of file diff --git a/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jar b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/test_subjects.jar new file mode 100644 index 0000000000000000000000000000000000000000..6e92989eb514a0db2bfae731ec2070c60e7261ec GIT binary patch literal 3956 zcmb7{byO7Ww#Mlc1OW*tL8L)Kdgzj&Q@R;KkQh=#i6I0AVFu}tp(F*QL%O?R0Kt(N z5C>^R!VCAF`+c6X);VjP=Uwkwd#`stZ|%Q+`apaFS{xD*5**@ZF+iL@M2bUzqoZc5 zB=|^IUHEbs2L~TVA4p7cU4i$HGW~yRroF28%dDfM`$%2Q$XG~6{ZMCgL>nk5G(`av zIz#hn{`*_7Uaf+i{lI?y4(`s5U_W8wtHkL45+TPY_I`eekKr$)|^PG78;Z+GR#r!9y0}c-Ho6DKch; zBzBlf^ZD&k3&uY?XN@7DM&!G`FnHI^>LS|5%+Ake4MdH=SMm(rUunEus_n;Qp0_E**!fZ;4KFJ2z2|t#<;6{!aZRO8K1#4)1{MfLh+=hi(F3mG4Z z-YVl)l8naVKXV)khd7a|X~q&nxJ3YFHRxkfv>af8GzeiG^Ij7^tR-CvspRUBHP13T z>9C}V477$}?fOtOu>KU_b=N|Y#Xbl9yxjDiG--_$%J{o&vIGn!nX zE6Z1AID`w3@*p79b#VFfX)|O7G7~fcY21VY@5kxUuXVKwozp7J?EBHL^_O~xt58Y@{;%`=l;dSN+p$D>c zKj1Vr(sR7+q9XZ(M>w(xDOGue{=vuzjzw3kvNfmU_dWG!0=*c8=A5PN?>d9slN>Zl z=%l`g+QH}HZ{yxgxeiv3&;TVjg{kVe5kzqtsEolyp7@P>ieD=4uHTGFjOfoW@y89o ze_?AI$ag!QcxnIaz~XtpREbB`VT@5%Rb{Pzeehvcx#HY!?4UR>5Mpkc7TSy)a@yV? znOTt`RihhjI3jgkE~@yHQObYlloueww5jy;2e(XQYl;>rf7@*Xm{o~O!fnQ!do*9n zUmZr;9)8eznwro^#3`J5Gu}Zisj$WSxc8#=!o>_5PEb#!FV0mb#QCxR_1%ce{)+5z z4nJ8fAE^jF8y7%Z*zG;O(Sr%e5`isdkn*5<7l;#7Crt*O&vc~H_{s4eu9wAO+68rf z>{Ux7Z6TFwdB{XYsi;6U@;1|mM~c$(g8dtdhP~`PHz$5o((j9=7HPxd;p3o(d8}2l zzMi;dJF$hVbgrFTR`orGi^V9zM0)gn@vQqWcyjI7+!|`~rlPDNb~S+c#C-ljFW;U4 zn%Lv*B^TYDdABKA#M$v!M4Lr9IrNyRS|vT;#*7A($5vDP7X22BtinU)BOTuMY1Jf6 zS|&1P`s9{~OJ02vc{I;yHM6=1MXHt1vd4$Jqos^$KrD}zYZI7?K5%xP!XC}u;V|0H z;*a&`%yG)pDK?CJ{B6d4HPc~+t0*_W0<28PV%(47HS|hG5sEj_rrlC!bfYh8#_h=F z#s{aA$A@|}Ef38--0Grp#|PT6AzJWQ1Z7N>_v~%^VzLtrl*Q<$<8?>UW!&simpeL7 zXP+~gW+81AGgrbgEM+lLE@K;?NSg$|EpMunN@ddv16)lsky5S9*kWj60#n?twq?b{smsT2J@%|$Y&)&5gE5v4aS-yLoJYj$e;Nb~S zK2^mH!D!~9KB_A&O!8XpnLrb>CGY0Hw7#K(YsuNmFfscUMW#EO?Mi27m*8Gu&fi&b zdTo-g=9Ou2t`^vWoA{uwvv>r$JeiJ<;A)da>|!@E^*|T zO{bvrI-YHxs@@=D*^F({ri{L{(l&kuRD>G#MdUcev{=>?X(YyaRJ8_%P&F`kmT9iX z?ng*uFl#PJ)*2;h*E;^=wlN;g;7g6l(!;#IH4tvL$J2 zw?vFB-;wQ$*U4L=n<6W${mOFPub;sO8#?Ch$z44y1xlFGJK9q1Dc<}N0Bo9Qb z;sv%|_{&`TMK+-SvAiNIN$hs6{Ec`?KZL_YJIY?5jwuZW}Yvi{&$Q6H)1VeUn^6LMoJr- zE4{Mb*^HEe%$ka}pxc!TEg-ZQa9i+SuxY0}*X;?j_-vrEXp7p$Ey?1;_Rl zGHTeKS+Lf(jfEu}=#K-(UrKFvXH01wP8b?7##|4J9l2$H^_I9FL4JoReeq*i-o(xk zB0I+=I4nL96E1XLjtVGqu6a}yIBf|_i)^SU21ycu>KQUUq_ z&O$8Z)WS{I1kmznps#@OwV3m#s-*IENg;XuKY9d=2*pjty?mY*uZ)T9l&CzTzP%Iw zJ~0h|&>Z8swL}}pYlQ!V#TR1xrA^>q{!HPTcD^6|_{Mw#2gif{PtI5fh3HTZC2i-I*zPK^n$Bcod@kMM}6jG2!Jrd>w6>aJxwAOBf@@!&N~{;Ty* zpu2|0zXiAo|FZsplK;BHpRKwEzrWRU75--o{By7C=KlG}>*oF~ev1F5)%rj}qH8a~ O)wqj`g9D(v&i)4!!XsP& literal 0 HcmV?d00001 diff --git a/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java new file mode 100644 index 00000000000000..7f38c9ea617ddd --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/classes_for_testing_type_inference/testsubjects/TestSubject.java @@ -0,0 +1,212 @@ +// 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 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 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(); + } + } + + public double testWithDoubleTypes() { + double result = 1; + for (double i = 1; i < 22; i = i + 1) { + System.out.println(i); + result += i; + } + return result; + } + + public float testWithFloatAndDoubleTypes() { + float result = 1; + for (double i = 1; i < 22; i = i + 1) { + System.out.println(i); + result += (float) i; + } + return result; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/companion_class_with_lambdas_in_implemented_interface_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/companion_class_with_lambdas_in_implemented_interface_disassembled_golden.txt new file mode 100644 index 00000000000000..538e2365c8071c --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/companion_class_with_lambdas_in_implemented_interface_disassembled_golden.txt @@ -0,0 +1,17 @@ +public abstract class com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod$$CC implements com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod { + public com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod$$CC(); + public java.util.List defaultMethodReference(java.util.List); + public static java.util.List defaultMethodReference$$dflt$$(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.util.List); + public java.lang.String defaultInvokingBootclasspathMethods(java.lang.String); + public static java.lang.String defaultInvokingBootclasspathMethods$$dflt$$(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.lang.String); + public java.util.List staticMethodReference(java.util.List); + public static java.util.List staticMethodReference$$dflt$$(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.util.List); + public java.util.List lambdaCallsDefaultMethod(java.util.List); + public static java.util.List lambdaCallsDefaultMethod$$dflt$$(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.util.List); + public static boolean startsWithA$$STATIC$$(java.lang.String); + public boolean startsWithS(java.lang.String); + public static boolean startsWithS$$dflt$$(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.lang.String); + public boolean startsWithS(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.lang.String); + public static boolean startsWithS$$dflt$$(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.lang.String); + static boolean lambda$lambdaCallsDefaultMethod$0$InterfaceMethod$$CC(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.lang.String); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/companion_with_static_and_default_methods_with_args_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/companion_with_static_and_default_methods_with_args_disassembled_golden.txt new file mode 100644 index 00000000000000..2a096efdc82846 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/companion_with_static_and_default_methods_with_args_disassembled_golden.txt @@ -0,0 +1,16 @@ + RuntimeVisibleAnnotations: + 0: #9() +-- + RuntimeVisibleAnnotations: + 0: #9() + RuntimeVisibleTypeAnnotations: + 0: #10(): METHOD_RETURN + 1: #10(): METHOD_FORMAL_PARAMETER, param_index=1 + RuntimeVisibleParameterAnnotations: + parameter 0: + parameter 1: + 0: #9() + MethodParameters: + Name Flags + x + y diff --git a/src/test/java/com/google/devtools/build/android/desugar/desugar_deps_consistency_test.sh b/src/test/java/com/google/devtools/build/android/desugar/desugar_deps_consistency_test.sh new file mode 100755 index 00000000000000..8e8707a07f925d --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/desugar_deps_consistency_test.sh @@ -0,0 +1,45 @@ +#!/bin/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. +set -eu +set -o pipefail + +SINGLEJAR="$1" +shift +JAR="$1" +shift + +out="$(mktemp)" +if ! "${SINGLEJAR}" --output "${out}" --check_desugar_deps --sources "$@"; then + # If output was generated, clean it up + if [ -e "${out}" ]; then rm "${out}"; fi + case "$0" in + *_fail_test) echo "Singlejar failed as expected!"; exit 0;; + esac + echo "Singlejar unexpectedly failed" + exit 1 +fi + +case "$0" in + *_fail_test) rm "${out}"; echo "Singlejar unexpectedly succeeded :("; exit 1;; +esac + +if "${JAR}" tf "${out}" | grep 'desugar_deps'; then + rm "${out}" + echo "Singlejar output unexpectedly contains desugaring metadata" + exit 2 +fi # else grep didn't find anything -> pass +rm "${out}" +exit 0 diff --git a/src/test/java/com/google/devtools/build/android/desugar/desugar_unused_synthetic_close_resource_method_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/desugar_unused_synthetic_close_resource_method_golden.txt new file mode 100644 index 00000000000000..4be60b6acee333 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/desugar_unused_synthetic_close_resource_method_golden.txt @@ -0,0 +1,8 @@ +Compiled from "UnusedSyntheticCloseResourceMethod.java" +public class com.google.devtools.build.android.desugar.testdata.UnusedSyntheticCloseResourceMethod { + public com.google.devtools.build.android.desugar.testdata.UnusedSyntheticCloseResourceMethod(); + Code: + 0: aload_0 + 1: invokespecial #9 // Method java/lang/Object."":()V + 4: return +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/diff.sh b/src/test/java/com/google/devtools/build/android/desugar/diff.sh new file mode 100755 index 00000000000000..afe2d96f7d8176 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/diff.sh @@ -0,0 +1,16 @@ +#!/bin/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. +diff "$1" "$2" diff --git a/src/test/java/com/google/devtools/build/android/desugar/interface_with_desugared_method_bodies_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/interface_with_desugared_method_bodies_disassembled_golden.txt new file mode 100644 index 00000000000000..a10c8b5b6bd65d --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/interface_with_desugared_method_bodies_disassembled_golden.txt @@ -0,0 +1,9 @@ +Compiled from "InterfaceMethod.java" +public interface com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod { + public abstract java.util.List defaultMethodReference(java.util.List); + public abstract java.lang.String defaultInvokingBootclasspathMethods(java.lang.String); + public abstract java.util.List staticMethodReference(java.util.List); + public abstract java.util.List lambdaCallsDefaultMethod(java.util.List); + public abstract boolean startsWithS(java.lang.String); + public abstract boolean startsWithS(com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod, java.lang.String); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/interface_with_static_and_default_methods_with_args_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/interface_with_static_and_default_methods_with_args_disassembled_golden.txt new file mode 100644 index 00000000000000..1cdce860c93bec --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/interface_with_static_and_default_methods_with_args_disassembled_golden.txt @@ -0,0 +1,13 @@ + RuntimeVisibleAnnotations: + 0: #18() + RuntimeVisibleTypeAnnotations: + 0: #19(): METHOD_RETURN + 1: #19(): METHOD_FORMAL_PARAMETER, param_index=1 + RuntimeVisibleParameterAnnotations: + parameter 0: + parameter 1: + 0: #18() + MethodParameters: + Name Flags + x + y diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/BUILD b/src/test/java/com/google/devtools/build/android/desugar/io/BUILD new file mode 100644 index 00000000000000..a8701c660f0166 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/BUILD @@ -0,0 +1,39 @@ +load("@rules_java//java:defs.bzl", "java_test") + +# Description: +# Tests for the Java 8 desugaring tool for Android. +package( + default_testonly = 1, +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["**"]), + visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__pkg__"], +) + +java_test( + name = "FieldInfoTest", + size = "small", + srcs = ["FieldInfoTest.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "IndexedInputsTest", + size = "small", + srcs = ["IndexedInputsTest.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/FieldInfoTest.java b/src/test/java/com/google/devtools/build/android/desugar/io/FieldInfoTest.java new file mode 100644 index 00000000000000..05798223283df6 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/FieldInfoTest.java @@ -0,0 +1,33 @@ +// 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.io; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link FieldInfo} */ +@RunWith(JUnit4.class) +public class FieldInfoTest { + + @Test + public void testFieldsAreCorrectlySet() { + FieldInfo info = FieldInfo.create("owner", "name", "desc"); + assertThat(info.owner()).isEqualTo("owner"); + assertThat(info.name()).isEqualTo("name"); + assertThat(info.desc()).isEqualTo("desc"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/FileBasedTypeReferenceClosureTest.java b/src/test/java/com/google/devtools/build/android/desugar/io/FileBasedTypeReferenceClosureTest.java new file mode 100644 index 00000000000000..33ac57725f6549 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/FileBasedTypeReferenceClosureTest.java @@ -0,0 +1,137 @@ +/* + * Copyright 2020 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.io; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link FileBasedTypeReferenceClosure}. + * + *

    The set of tests is based on the following type reference graph. For example A(lpha) contains + * direct type symbol references to B(ravo) and C(harlie). + * + *

    + *     A
    + *     ↓ ↘
    + *     B  C
    + *      ↘ ↕
    + *        D
    + *        ↓
    + *        E
    + * 
    + */ +@RunWith(JUnit4.class) +public class FileBasedTypeReferenceClosureTest { + + private final ClassName alpha = ClassName.create("desugar/io/testlib/Alpha"); + private final ClassName bravo = ClassName.create("desugar/io/testlib/Bravo"); + private final ClassName charlie = ClassName.create("desugar/io/testlib/Charlie"); + private final ClassName delta = ClassName.create("desugar/io/testlib/Delta"); + private final ClassName echo = ClassName.create("desugar/io/testlib/Echo"); + private final ClassName implicitTypeRef = + ClassName.create("desugar/io/testlib/ImplicitTypeReferenceSource"); + + @Test + public void findReachableReferencedTypes_fromEmpty() { + FileBasedTypeReferenceClosure fileBasedTypeReferenceClosure = + new FileBasedTypeReferenceClosure( + className -> className.hasAnyPackagePrefix("desugar/io/testlib/"), + new ResourceBasedClassFiles()); + assertThat(fileBasedTypeReferenceClosure.findReachableReferencedTypes(ImmutableSet.of())) + .isEmpty(); + } + + @Test + public void findReachableReferencedTypes_fromAlpha() { + FileBasedTypeReferenceClosure fileBasedTypeReferenceClosure = + new FileBasedTypeReferenceClosure( + className -> className.hasAnyPackagePrefix("desugar/io/testlib/"), + new ResourceBasedClassFiles()); + assertThat(fileBasedTypeReferenceClosure.findReachableReferencedTypes(ImmutableSet.of(alpha))) + .containsExactly(alpha, bravo, charlie, delta, echo); + } + + @Test + public void findReachableReferencedTypes_fromBravo() { + FileBasedTypeReferenceClosure fileBasedTypeReferenceClosure = + new FileBasedTypeReferenceClosure( + className -> className.hasAnyPackagePrefix("desugar/io/testlib/"), + new ResourceBasedClassFiles()); + assertThat(fileBasedTypeReferenceClosure.findReachableReferencedTypes(ImmutableSet.of(bravo))) + .containsExactly(bravo, charlie, delta, echo); + } + + @Test + public void findReachableReferencedTypes_fromCharlie() { + FileBasedTypeReferenceClosure fileBasedTypeReferenceClosure = + new FileBasedTypeReferenceClosure( + className -> className.hasAnyPackagePrefix("desugar/io/testlib/"), + new ResourceBasedClassFiles()); + assertThat(fileBasedTypeReferenceClosure.findReachableReferencedTypes(ImmutableSet.of(charlie))) + .containsExactly(charlie, delta, echo); + } + + @Test + public void findReachableReferencedTypes_fromDelta() { + FileBasedTypeReferenceClosure fileBasedTypeReferenceClosure = + new FileBasedTypeReferenceClosure( + className -> className.hasAnyPackagePrefix("desugar/io/testlib/"), + new ResourceBasedClassFiles()); + assertThat(fileBasedTypeReferenceClosure.findReachableReferencedTypes(ImmutableSet.of(delta))) + .containsExactly(charlie, delta, echo); + } + + @Test + public void findReachableReferencedTypes_fromEcho() { + FileBasedTypeReferenceClosure fileBasedTypeReferenceClosure = + new FileBasedTypeReferenceClosure( + className -> className.hasAnyPackagePrefix("desugar/io/testlib/"), + new ResourceBasedClassFiles()); + assertThat(fileBasedTypeReferenceClosure.findReachableReferencedTypes(ImmutableSet.of(echo))) + .containsExactly(echo); + } + + @Test + public void findReachableReferencedTypes_fromAll() { + FileBasedTypeReferenceClosure fileBasedTypeReferenceClosure = + new FileBasedTypeReferenceClosure( + className -> className.hasAnyPackagePrefix("desugar/io/testlib/"), + new ResourceBasedClassFiles()); + assertThat( + fileBasedTypeReferenceClosure.findReachableReferencedTypes( + ImmutableSet.of(alpha, bravo, charlie, delta, echo))) + .containsExactly(alpha, bravo, charlie, delta, echo); + } + + @Test + public void findReachableReferencedTypes_implicitTypeReference() { + FileBasedTypeReferenceClosure fileBasedTypeReferenceClosure = + new FileBasedTypeReferenceClosure( + className -> className.hasAnyPackagePrefix("desugar/io/testlib/", "java/io/"), + new ResourceBasedClassFiles()); + ImmutableSet reachableReferencedTypes = + fileBasedTypeReferenceClosure.findReachableReferencedTypes( + ImmutableSet.of(implicitTypeRef)); + assertThat(reachableReferencedTypes).contains(ClassName.create("java/io/PrintStream")); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/IndexedInputsTest.java b/src/test/java/com/google/devtools/build/android/desugar/io/IndexedInputsTest.java new file mode 100644 index 00000000000000..c9578679167c48 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/IndexedInputsTest.java @@ -0,0 +1,128 @@ +// 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.io; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import java.io.File; +import java.io.FileOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test that exercises the behavior of the IndexedInputs class. */ +@RunWith(JUnit4.class) +public final class IndexedInputsTest { + + private static File lib1; + + private static String lib1Name; + + private static File lib2; + + private static String lib2Name; + + private static InputFileProvider lib1InputFileProvider; + + private static InputFileProvider lib2InputFileProvider; + + @BeforeClass + public static void setUpClass() throws Exception { + lib1 = File.createTempFile("lib1", ".jar"); + lib1Name = lib1.getName(); + try (ZipOutputStream zos1 = new ZipOutputStream(new FileOutputStream(lib1))) { + zos1.putNextEntry(new ZipEntry("a/b/C.class")); + zos1.putNextEntry(new ZipEntry("a/b/D.class")); + zos1.closeEntry(); + } + + lib2 = File.createTempFile("lib2", ".jar"); + lib2Name = lib2.getName(); + try (ZipOutputStream zos2 = new ZipOutputStream(new FileOutputStream(lib2))) { + zos2.putNextEntry(new ZipEntry("a/b/C.class")); + zos2.putNextEntry(new ZipEntry("a/b/E.class")); + zos2.closeEntry(); + } + } + + @Before + public void createProviders() throws Exception { + lib1InputFileProvider = new ZipInputFileProvider(lib1.toPath()); + lib2InputFileProvider = new ZipInputFileProvider(lib2.toPath()); + } + + @After + public void closeProviders() throws Exception { + lib1InputFileProvider.close(); + lib2InputFileProvider.close(); + } + + @AfterClass + public static void tearDownClass() throws Exception { + lib1.delete(); + lib2.delete(); + } + + @Test + public void testClassFoundWithParentLibrary() throws Exception { + IndexedInputs indexedLib2 = new IndexedInputs(ImmutableList.of(lib2InputFileProvider)); + IndexedInputs indexedLib1 = new IndexedInputs(ImmutableList.of(lib1InputFileProvider)); + IndexedInputs indexedLib2AndLib1 = indexedLib1.withParent(indexedLib2); + assertThat(indexedLib2AndLib1.getInputFileProvider("a/b/C.class").toString()) + .isEqualTo(lib2Name); + assertThat(indexedLib2AndLib1.getInputFileProvider("a/b/D.class").toString()) + .isEqualTo(lib1Name); + assertThat(indexedLib2AndLib1.getInputFileProvider("a/b/E.class").toString()) + .isEqualTo(lib2Name); + + indexedLib2 = new IndexedInputs(ImmutableList.of(lib2InputFileProvider)); + indexedLib1 = new IndexedInputs(ImmutableList.of(lib1InputFileProvider)); + IndexedInputs indexedLib1AndLib2 = indexedLib2.withParent(indexedLib1); + assertThat(indexedLib1AndLib2.getInputFileProvider("a/b/C.class").toString()) + .isEqualTo(lib1Name); + assertThat(indexedLib1AndLib2.getInputFileProvider("a/b/D.class").toString()) + .isEqualTo(lib1Name); + assertThat(indexedLib1AndLib2.getInputFileProvider("a/b/E.class").toString()) + .isEqualTo(lib2Name); + } + + @Test + public void testClassFoundWithoutParentLibrary() throws Exception { + IndexedInputs ijLib1Lib2 = + new IndexedInputs(ImmutableList.of(lib1InputFileProvider, lib2InputFileProvider)); + assertThat(ijLib1Lib2.getInputFileProvider("a/b/C.class").toString()).isEqualTo(lib1Name); + assertThat(ijLib1Lib2.getInputFileProvider("a/b/D.class").toString()).isEqualTo(lib1Name); + assertThat(ijLib1Lib2.getInputFileProvider("a/b/E.class").toString()).isEqualTo(lib2Name); + + IndexedInputs ijLib2Lib1 = + new IndexedInputs(ImmutableList.of(lib2InputFileProvider, lib1InputFileProvider)); + assertThat(ijLib2Lib1.getInputFileProvider("a/b/C.class").toString()).isEqualTo(lib2Name); + assertThat(ijLib2Lib1.getInputFileProvider("a/b/D.class").toString()).isEqualTo(lib1Name); + assertThat(ijLib2Lib1.getInputFileProvider("a/b/E.class").toString()).isEqualTo(lib2Name); + } + + @Test + public void testClassNotFound() throws Exception { + IndexedInputs ijLib1Lib2 = + new IndexedInputs(ImmutableList.of(lib1InputFileProvider, lib2InputFileProvider)); + assertThat(ijLib1Lib2.getInputFileProvider("a/b/F.class")).isNull(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Alpha.java b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Alpha.java new file mode 100644 index 00000000000000..4ae2891c7f4821 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Alpha.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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 desugar.io.testlib; + +/** + * Refer to the javadoc of {@link + * com.google.devtools.build.android.desugar.io.FileBasedTypeReferenceClosureTest} for node + * relationships in the graph. + */ +class Alpha { + public void process(Bravo b, Charlie c) {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Bravo.java b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Bravo.java new file mode 100644 index 00000000000000..2a4e263f7ad21f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Bravo.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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 desugar.io.testlib; + +/** + * Refer to the javadoc of {@link + * com.google.devtools.build.android.desugar.io.FileBasedTypeReferenceClosureTest} for node + * relationships in the graph. + */ +class Bravo { + public void process(Delta d) {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Charlie.java b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Charlie.java new file mode 100644 index 00000000000000..e519e47d95d38b --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Charlie.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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 desugar.io.testlib; + +/** + * Refer to the javadoc of {@link + * com.google.devtools.build.android.desugar.io.FileBasedTypeReferenceClosureTest} for node + * relationships in the graph. + */ +class Charlie { + public void process(Delta d) {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Delta.java b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Delta.java new file mode 100644 index 00000000000000..64e719c32edbf7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Delta.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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 desugar.io.testlib; + +/** + * Refer to the javadoc of {@link + * com.google.devtools.build.android.desugar.io.FileBasedTypeReferenceClosureTest} for node + * relationships in the graph. + */ +class Delta { + void process(Charlie c, Echo echo) {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Echo.java b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Echo.java new file mode 100644 index 00000000000000..25d09ff1f06c7f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/Echo.java @@ -0,0 +1,21 @@ +/* + * Copyright 2020 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 desugar.io.testlib; + +/** + * Refer to the javadoc of {@link + * com.google.devtools.build.android.desugar.io.FileBasedTypeReferenceClosureTest} for node + * relationships in the graph. + */ +class Echo {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/io/testlib/ImplicitTypeReferenceSource.java b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/ImplicitTypeReferenceSource.java new file mode 100644 index 00000000000000..494129ca5b8f3f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/io/testlib/ImplicitTypeReferenceSource.java @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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 desugar.io.testlib; + +/** Test sources for constructing a binary with implicit type reference. */ +public class ImplicitTypeReferenceSource { + public static void main(String[] args) { + System.out.println("Invoked with implicit Type Reference to java/io/PrintStream"); + } + + private ImplicitTypeReferenceSource() {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/jacoco_0_7_5_default_method.jar b/src/test/java/com/google/devtools/build/android/desugar/jacoco_0_7_5_default_method.jar new file mode 100644 index 0000000000000000000000000000000000000000..1e0deca5a8e7a7a363c5a3fc6057873cd105e62c GIT binary patch literal 1393 zcmWIWW@Zs#;Nak3Xo>yf#ef7j8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1g$M^D0kP&Ds;QCkE__$G4Zoy8!L*0Ai3UxEL6K zF3Uvo0t-+!IX_n)w+bGpiqwk4+=84`JSv6IRHi4E6(u66FF~_g0jv6~#Ii(-aSoUVsggW z^TG!WL&w!KL=;~%@7(QkeeG>oBOmF*tLqQNFIZo6Z&b-e*=!(UQD;V}i_u8FDUFYj}=&<*l^q4XWOAve?N=kfW+9 zMd_WMbW$i6d`F85{^;t9Alp7A5 zQQhl0&0SQcQuHj>|1-!I>zEpN`EwQ-Yv51**??t68Y_I|mxS?%Pbvv2R5*1f}5AY5K!^7DT6v_<9T zUueWP#!}7eqhib z>xAVvgnsl~qkzy1WZ7fYkDdb&!45JFIZ8lz5CLk5Fby?F26(fwf%LNj;boxi4rUM! E02KnJN&o-= literal 0 HcmV?d00001 diff --git a/src/test/java/com/google/devtools/build/android/desugar/jacoco_legacy_default_method_companion_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/jacoco_legacy_default_method_companion_disassembled_golden.txt new file mode 100644 index 00000000000000..d8f2889ff006a4 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/jacoco_legacy_default_method_companion_disassembled_golden.txt @@ -0,0 +1,37 @@ +public abstract class com.example.gavra.java8coverage.Defaults$$CC { + public static boolean[] $jacocoData; + + public static void foo$$dflt$$(com.example.gavra.java8coverage.Defaults); + Code: + 0: invokestatic #12 // Method $jacocoInit$$STATIC$$:()[Z + 3: astore_1 + 4: aload_1 + 5: iconst_0 + 6: iconst_1 + 7: bastore + 8: return + + public static void baz$$STATIC$$(); + Code: + 0: invokestatic #12 // Method $jacocoInit$$STATIC$$:()[Z + 3: astore_0 + 4: aload_0 + 5: iconst_1 + 6: iconst_1 + 7: bastore + 8: return + + static boolean[] $jacocoInit$$STATIC$$(); + Code: + 0: getstatic #18 // Field $jacocoData:[Z + 3: dup + 4: ifnonnull 21 + 7: pop + 8: ldc2_w #19 // long -7447229029980688604l + 11: ldc #22 // String com/example/gavra/java8coverage/Defaults + 13: iconst_2 + 14: invokestatic #28 // Method org/jacoco/agent/rt/internal_773e439/Offline.getProbes:(JLjava/lang/String;I)[Z + 17: dup + 18: putstatic #18 // Field $jacocoData:[Z + 21: areturn +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/lambda/LambdaDesugaringStackTraceTest.java b/src/test/java/com/google/devtools/build/android/desugar/lambda/LambdaDesugaringStackTraceTest.java new file mode 100644 index 00000000000000..7e98a50b6931aa --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/lambda/LambdaDesugaringStackTraceTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2021 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.lambda; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Test class for testing the stack trace related behaviors from lambda desugaring. */ +@RunWith(DesugarRunner.class) +public final class LambdaDesugaringStackTraceTest { + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, MethodHandles.lookup()) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .setWorkingJavaPackage( + "com.google.devtools.build.android.desugar.lambda.testsrc.stacktrace") + .enableIterativeTransformation(2) + .build(); + + @Test + public void stackTraceFileNamesThroughLambda_beforeDesugaring( + @RuntimeMethodHandle( + className = "StackTraceTestTarget", + memberName = "getStackTraceFileNamesThroughLambda", + round = 0) + MethodHandle stackTraceTestTarget) + throws Throwable { + List result = (List) stackTraceTestTarget.invoke(); + assertThat(result.subList(0, 4)) + .containsExactly( + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "LambdaDesugaringStackTraceTest.java") + .inOrder(); + } + + @Test + public void stackTraceFileNamesThroughLambda_afterDesugaring( + @RuntimeMethodHandle( + className = "StackTraceTestTarget", + memberName = "getStackTraceFileNamesThroughLambda", + round = 1) + MethodHandle stackTraceTestTarget) + throws Throwable { + List result = (List) stackTraceTestTarget.invoke(); + assertThat(result.subList(0, 5)) + .containsExactly( + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "LambdaDesugaringStackTraceTest.java") + .inOrder(); + } + + @Test + public void stackTraceFileNamesThroughLambda_afterDesugaringTwice( + @RuntimeMethodHandle( + className = "StackTraceTestTarget", + memberName = "getStackTraceFileNamesThroughLambda", + round = 2) + MethodHandle stackTraceTestTarget) + throws Throwable { + List result = (List) stackTraceTestTarget.invoke(); + assertThat(result.subList(0, 5)) + .containsExactly( + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "LambdaDesugaringStackTraceTest.java") + .inOrder(); + } + + @Test + public void stackTraceFileNamesThroughNestedLambda_afterDesugaring( + @RuntimeMethodHandle( + className = "StackTraceTestTarget", + memberName = "getStackTraceFileNamesThroughNestedLambda", + round = 1) + MethodHandle stackTraceTestTarget) + throws Throwable { + List result = (List) stackTraceTestTarget.invoke(); + assertThat(result.subList(0, 5)) + .containsExactly( + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "StackTraceTestTarget.java", + "LambdaDesugaringStackTraceTest.java") + .inOrder(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/lambda/testsrc/stacktrace/StackTraceTestTarget.java b/src/test/java/com/google/devtools/build/android/desugar/lambda/testsrc/stacktrace/StackTraceTestTarget.java new file mode 100644 index 00000000000000..1507424d0819ff --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/lambda/testsrc/stacktrace/StackTraceTestTarget.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 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.lambda.testsrc.stacktrace; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +/** Test data class for testing the stack trace related behaviors from lambda desugaring. */ +public class StackTraceTestTarget { + + private StackTraceTestTarget() {} + + public static List getStackTraceFileNames() { + Throwable throwable = new Throwable(); + List sourceFileNames = new ArrayList<>(); + for (StackTraceElement stackTraceElement : throwable.getStackTrace()) { + sourceFileNames.add(stackTraceElement.getFileName()); + } + return sourceFileNames; + } + + public static List getStackTraceFileNamesThroughLambda() throws Exception { + Callable> stackTraceElementsProvider = () -> getStackTraceFileNames(); + return stackTraceElementsProvider.call(); + } + + public static List getStackTraceFileNamesThroughNestedLambda() throws Exception { + Callable>> stackTraceElementsProviderProvider = + () -> () -> getStackTraceFileNames(); + return stackTraceElementsProviderProvider.call().call(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/langmodel/BUILD b/src/test/java/com/google/devtools/build/android/desugar/langmodel/BUILD new file mode 100644 index 00000000000000..c5ca3c29ffc6a5 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/langmodel/BUILD @@ -0,0 +1,60 @@ +load("@rules_java//java:defs.bzl", "java_test") + +# Description: +# Tests for the Java 8 desugaring tool for Android. +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +java_test( + name = "ClassMemberKeyTest", + srcs = ["ClassMemberKeyTest.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "ClassMemberRecordTest", + srcs = ["ClassMemberRecordTest.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "ClassMemberTrackReasonTest", + srcs = ["ClassMemberTrackReasonTest.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +test_suite( + name = "langmodel_test", + tags = ["small"], +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberKeyTest.java b/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberKeyTest.java new file mode 100644 index 00000000000000..61c46858944905 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberKeyTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2019 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.langmodel; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link ClassMemberKey}. */ +@RunWith(JUnit4.class) +public class ClassMemberKeyTest { + + @Test + public void fieldKey_bridgeOfInstanceRead() { + FieldKey fieldKey = + FieldKey.create(ClassName.create("a/b/Charlie"), "instanceFieldOfLongType", "J"); + assertThat(fieldKey.bridgeOfInstanceRead()) + .isEqualTo( + MethodKey.create( + ClassName.create("a/b/Charlie"), + "instanceFieldOfLongType$bridge_getter", + "(La/b/Charlie;)J")); + } + + @Test + public void fieldKey_bridgeOfInstanceWrite() { + + FieldKey fieldKey = + FieldKey.create(ClassName.create("a/b/Charlie"), "instanceFieldOfLongType", "J"); + assertThat(fieldKey.bridgeOfInstanceWrite()) + .isEqualTo( + MethodKey.create( + ClassName.create("a/b/Charlie"), + "instanceFieldOfLongType$bridge_setter", + "(La/b/Charlie;J)J")); + } + + @Test + public void fieldKey_bridgeOfStaticRead() { + FieldKey fieldKey = + FieldKey.create(ClassName.create("a/b/Charlie"), "staticFieldOfLongType", "J"); + assertThat(fieldKey.bridgeOfStaticRead()) + .isEqualTo( + MethodKey.create( + ClassName.create("a/b/Charlie"), "staticFieldOfLongType$bridge_getter", "()J")); + } + + @Test + public void fieldKey_bridgeOfStaticWrite() { + FieldKey fieldKey = + FieldKey.create(ClassName.create("a/b/Charlie"), "staticFieldOfLongType", "J"); + assertThat(fieldKey.bridgeOfStaticWrite()) + .isEqualTo( + MethodKey.create( + ClassName.create("a/b/Charlie"), "staticFieldOfLongType$bridge_setter", "(J)J")); + } + + @Test + public void methodKey_bridgeOfClassInstanceMethod() { + MethodKey methodKey = MethodKey.create(ClassName.create("a/b/Charlie"), "twoLongSum", "(JJ)J"); + assertThat(methodKey.bridgeOfClassInstanceMethod()) + .isEqualTo( + MethodKey.create( + ClassName.create("a/b/Charlie"), "twoLongSum$bridge", "(La/b/Charlie;JJ)J")); + } + + @Test + public void methodKey_bridgeOfClassStaticMethod() { + MethodKey methodKey = MethodKey.create(ClassName.create("a/b/Charlie"), "twoLongSum", "(JJ)J"); + assertThat(methodKey.bridgeOfClassStaticMethod()) + .isEqualTo(MethodKey.create(ClassName.create("a/b/Charlie"), "twoLongSum$bridge", "(JJ)J")); + } + + @Test + public void methodKey_bridgeOfConstructor() { + MethodKey methodKey = MethodKey.create(ClassName.create("a/b/Charlie"), "", "(JJ)V"); + assertThat(methodKey.bridgeOfConstructor(ClassName.create("a/b/Charlie$NestCC"))) + .isEqualTo( + MethodKey.create( + ClassName.create("a/b/Charlie"), "", "(JJLa/b/Charlie$NestCC;)V")); + } + + @Test + public void methodKey_substituteOfInterfaceInstanceMethod() { + MethodKey methodKey = + MethodKey.create(ClassName.create("a/b/Charlie"), "instanceInstanceMethod", "(JJ)J"); + assertThat(methodKey.substituteOfInterfaceInstanceMethod()) + .isEqualTo( + MethodKey.create( + ClassName.create("a/b/Charlie"), "instanceInstanceMethod", "(La/b/Charlie;JJ)J")); + } + + @Test + public void methodKey_substituteOfInterfaceStaticMethod() { + + MethodKey methodKey = + MethodKey.create(ClassName.create("a/b/Charlie"), "instanceStaticMethod", "(JJ)J"); + assertThat(methodKey.substituteOfInterfaceStaticMethod()) + .isEqualTo( + MethodKey.create(ClassName.create("a/b/Charlie"), "instanceStaticMethod", "(JJ)J")); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecordTest.java b/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecordTest.java new file mode 100644 index 00000000000000..c0dd0843210c26 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecordTest.java @@ -0,0 +1,201 @@ +/* + * Copyright 2019 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.langmodel; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord.ClassMemberRecordBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.Opcodes; + +/** Tests for {@link ClassMemberRecord}. */ +@RunWith(JUnit4.class) +public final class ClassMemberRecordTest { + + private final ClassMemberRecordBuilder classMemberRecord = ClassMemberRecord.builder(); + + @Test + public void trackFieldUse() { + FieldKey classMemberKey = + FieldKey.create(ClassName.create("package/path/OwnerClass"), "fieldOfPrimitiveLong", "J"); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.GETFIELD); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.PUTFIELD); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_GETFIELD); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_PUTFIELD); + + assertThat(classMemberRecord.build().findAllMemberUseKind(classMemberKey)) + .containsExactly( + MemberUseKind.GETFIELD, + MemberUseKind.PUTFIELD, + MemberUseKind.H_GETFIELD, + MemberUseKind.H_PUTFIELD); + } + + @Test + public void trackConstructorUse() { + MethodKey classMemberKey = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "", "()V"); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.INVOKESPECIAL); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_NEWINVOKESPECIAL); + + assertThat(classMemberRecord.build().findAllMemberUseKind(classMemberKey)) + .containsExactly(MemberUseKind.INVOKESPECIAL, MemberUseKind.H_NEWINVOKESPECIAL); + } + + @Test + public void trackMethodUse() { + MethodKey classMemberKey = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method", "(II)I"); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.INVOKEVIRTUAL); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.INVOKESPECIAL); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.INVOKESTATIC); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.INVOKEINTERFACE); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.INVOKEDYNAMIC); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_INVOKEVIRTUAL); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_INVOKESTATIC); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_INVOKESPECIAL); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_NEWINVOKESPECIAL); + classMemberRecord.logMemberUse(classMemberKey, Opcodes.H_INVOKEINTERFACE); + + assertThat(classMemberRecord.build().findAllMemberUseKind(classMemberKey)) + .containsExactly( + MemberUseKind.INVOKEVIRTUAL, + MemberUseKind.INVOKESPECIAL, + MemberUseKind.INVOKESTATIC, + MemberUseKind.INVOKEINTERFACE, + MemberUseKind.INVOKEDYNAMIC, + MemberUseKind.H_INVOKEVIRTUAL, + MemberUseKind.H_INVOKESTATIC, + MemberUseKind.H_INVOKESPECIAL, + MemberUseKind.H_NEWINVOKESPECIAL, + MemberUseKind.H_INVOKEINTERFACE); + } + + @Test + public void trackMemberDeclaration() { + MethodKey classMemberKey = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method", "(II)I"); + classMemberRecord.logMemberDecl( + classMemberKey, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE); + + ClassMemberRecord readOnlyClassMemberRecord = classMemberRecord.build(); + assertThat(readOnlyClassMemberRecord.findOwnerAccessCode(classMemberKey)) + .isEqualTo(Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER); + assertThat(readOnlyClassMemberRecord.findMemberAccessCode(classMemberKey)) + .isEqualTo(Opcodes.ACC_PRIVATE); + } + + @Test + public void trackMemberDeclaration_withDeprecatedAnnotation() { + MethodKey classMemberKey = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method", "(II)I"); + classMemberRecord.logMemberDecl( + classMemberKey, + Opcodes.ACC_DEPRECATED | Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, + Opcodes.ACC_DEPRECATED | Opcodes.ACC_PRIVATE); + ClassMemberRecord rawClassMemberRecord = classMemberRecord.build(); + assertThat(rawClassMemberRecord.findOwnerAccessCode(classMemberKey)) + .isEqualTo(Opcodes.ACC_DEPRECATED | Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER); + assertThat(rawClassMemberRecord.findMemberAccessCode(classMemberKey)) + .isEqualTo(Opcodes.ACC_DEPRECATED | Opcodes.ACC_PRIVATE); + } + + @Test + public void mergeRecord_trackingReasons() { + ClassMemberRecordBuilder otherClassMemberRecord = ClassMemberRecord.builder(); + + MethodKey method1 = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method1", "(II)I"); + MethodKey method2 = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method2", "(II)I"); + MethodKey method3 = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method3", "(II)I"); + + classMemberRecord.logMemberDecl(method1, Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE); + classMemberRecord.logMemberUse(method2, Opcodes.INVOKEVIRTUAL); + + otherClassMemberRecord.logMemberDecl(method2, Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE); + otherClassMemberRecord.logMemberUse(method2, Opcodes.INVOKESPECIAL); + otherClassMemberRecord.logMemberUse(method3, Opcodes.INVOKEVIRTUAL); + + ClassMemberRecord mergedRecord = + classMemberRecord.mergeFrom(otherClassMemberRecord.build()).build(); + + assertThat(mergedRecord.hasDeclReason(method1)).isTrue(); + assertThat(mergedRecord.findOwnerAccessCode(method1)).isEqualTo(Opcodes.ACC_SUPER); + assertThat(mergedRecord.findMemberAccessCode(method1)).isEqualTo(Opcodes.ACC_PRIVATE); + assertThat(mergedRecord.findAllMemberUseKind(method1)).isEmpty(); + + assertThat(mergedRecord.hasDeclReason(method2)).isTrue(); + assertThat(mergedRecord.findOwnerAccessCode(method2)).isEqualTo(Opcodes.ACC_SUPER); + assertThat(mergedRecord.findMemberAccessCode(method2)).isEqualTo(Opcodes.ACC_PRIVATE); + assertThat(mergedRecord.findAllMemberUseKind(method2)) + .containsExactly(MemberUseKind.INVOKEVIRTUAL, MemberUseKind.INVOKESPECIAL); + + assertThat(mergedRecord.hasDeclReason(method3)).isFalse(); + assertThat(mergedRecord.findOwnerAccessCode(method3)).isEqualTo(0); + assertThat(mergedRecord.findMemberAccessCode(method3)).isEqualTo(0); + assertThat(mergedRecord.findAllMemberUseKind(method3)) + .containsExactly(MemberUseKind.INVOKEVIRTUAL); + } + + @Test + public void filterUsedMemberWithTrackedDeclaration_noMemberDeclaration() { + MethodKey classMemberKey = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method", "(II)I"); + + classMemberRecord.logMemberUse(classMemberKey, Opcodes.INVOKEVIRTUAL); + ClassMemberRecord rawClassMemberRecord = classMemberRecord.build(); + assertThat(rawClassMemberRecord.hasTrackingReason(classMemberKey)).isTrue(); + + ClassMemberRecord filteredRecord = + rawClassMemberRecord.filterUsedMemberWithTrackedDeclaration(); + assertThat(filteredRecord.hasTrackingReason(classMemberKey)).isFalse(); + } + + @Test + public void filterUsedMemberWithTrackedDeclaration_noMemberUse() { + MethodKey classMemberKey = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method", "(II)I"); + + classMemberRecord.logMemberDecl( + classMemberKey, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE); + ClassMemberRecord rawClassMemberRecord = classMemberRecord.build(); + assertThat(rawClassMemberRecord.hasTrackingReason(classMemberKey)).isTrue(); + + ClassMemberRecord filteredRecord = + rawClassMemberRecord.filterUsedMemberWithTrackedDeclaration(); + assertThat(filteredRecord.hasTrackingReason(classMemberKey)).isFalse(); + } + + @Test + public void filterUsedMemberWithTrackedDeclaration_interfaceMemberWithoutUse_shouldTrack() { + MethodKey classMemberKey = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method", "(II)I"); + + classMemberRecord.logMemberDecl( + classMemberKey, Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, Opcodes.ACC_PRIVATE); + ClassMemberRecord rawClassMemberRecord = classMemberRecord.build(); + assertThat(rawClassMemberRecord.hasTrackingReason(classMemberKey)).isTrue(); + + ClassMemberRecord filteredRecord = + rawClassMemberRecord.filterUsedMemberWithTrackedDeclaration(); + assertThat(filteredRecord.hasTrackingReason(classMemberKey)).isTrue(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberTrackReasonTest.java b/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberTrackReasonTest.java new file mode 100644 index 00000000000000..0291ffb0a396ef --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberTrackReasonTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2019 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.langmodel; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.Opcodes; + +/** Tests for {@link ClassMemberTrackReason}. */ +@RunWith(JUnit4.class) +public class ClassMemberTrackReasonTest { + + private static final ImmutableList MEMBER_USE_OPCODES = + ImmutableList.of( + 0, // default unknown value. + Opcodes.H_GETFIELD, + Opcodes.H_GETSTATIC, + Opcodes.H_PUTFIELD, + Opcodes.H_PUTSTATIC, + Opcodes.H_INVOKEVIRTUAL, + Opcodes.H_INVOKESTATIC, + Opcodes.H_INVOKESPECIAL, + Opcodes.H_NEWINVOKESPECIAL, + Opcodes.H_INVOKEINTERFACE, + Opcodes.GETSTATIC, + Opcodes.PUTSTATIC, + Opcodes.GETFIELD, + Opcodes.PUTFIELD, + Opcodes.INVOKEVIRTUAL, + Opcodes.INVOKESPECIAL, + Opcodes.INVOKESTATIC, + Opcodes.INVOKEINTERFACE, + Opcodes.INVOKEDYNAMIC); + + @Test + public void memberUseKind_toOpcodeValues() { + List backingOpcodes = + Arrays.stream(MemberUseKind.values()) + .map(MemberUseKind::getOpcode) + .collect(Collectors.toList()); + assertThat(backingOpcodes).containsExactlyElementsIn(MEMBER_USE_OPCODES).inOrder(); + } + + @Test + public void memberUseKind_fromOpcodeValues() { + List allParsedMemberUseKinds = + MEMBER_USE_OPCODES.stream().map(MemberUseKind::fromValue).collect(Collectors.toList()); + assertThat(allParsedMemberUseKinds).containsExactlyElementsIn(MemberUseKind.values()).inOrder(); + } + + @Test + public void memberUseKind_invertibleMapping() { + assertThat( + MEMBER_USE_OPCODES.stream() + .map(MemberUseKind::fromValue) + .map(MemberUseKind::getOpcode) + .collect(Collectors.toList())) + .containsExactlyElementsIn(MEMBER_USE_OPCODES) + .inOrder(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/mocked_android_framework/android/os/Build.java b/src/test/java/com/google/devtools/build/android/desugar/mocked_android_framework/android/os/Build.java new file mode 100644 index 00000000000000..43d2f7df08dea4 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/mocked_android_framework/android/os/Build.java @@ -0,0 +1,31 @@ +// 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 android.os; + +/** This class is a standin for android.os.Build for tests running in a JVM */ +public final class Build { + + public static final String SYSTEM_PROPERTY_NAME = "fortest.simulated.android.sdk_int"; + + /** A simple mock for the real android.os.Build.VERSION */ + public static final class VERSION { + + public static final int SDK_INT; + + static { + String sdkInt = System.getProperty(SYSTEM_PROPERTY_NAME, "0"); + SDK_INT = Integer.parseInt(sdkInt); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/BUILD b/src/test/java/com/google/devtools/build/android/desugar/runtime/BUILD new file mode 100644 index 00000000000000..6ab7673ce39c38 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/BUILD @@ -0,0 +1,115 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_test") + +# Description: +# Tests for the extension classes for desugaring try-with-resources for Android. +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) + +java_library( + name = "throwable_extension_test_utility", + srcs = ["ThrowableExtensionTestUtility.java"], + deps = [ + "//third_party:guava", + "//third_party:truth", + ], +) + +java_test( + name = "ThrowableExtensionTestWithMimicDesugaringStrategy", + size = "small", + srcs = ["ThrowableExtensionTest.java"], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=18", + "-Dexpected.strategy='com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$MimicDesugaringStrategy'", + ], + test_class = "com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTest", + deps = [ + ":throwable_extension_test_utility", + "//src/test/java/com/google/devtools/build/android/desugar:mocked_android_os_sdk_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "ThrowableExtensionTestWithNullDesugaringStrategy", + size = "small", + srcs = ["ThrowableExtensionTest.java"], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=18", + "-Dcom.google.devtools.build.android.desugar.runtime.twr_disable_mimic=true", + "-Dexpected.strategy='com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$NullDesugaringStrategy'", + ], + test_class = "com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTest", + deps = [ + ":throwable_extension_test_utility", + "//src/test/java/com/google/devtools/build/android/desugar:mocked_android_os_sdk_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "ThrowableExtensionTestWithReuseDesugaringStrategy", + size = "small", + srcs = ["ThrowableExtensionTest.java"], + jvm_flags = [ + "-Dfortest.simulated.android.sdk_int=19", + "-Dexpected.strategy='com.google.devtools.build.android.desugar.runtime.ThrowableExtension$$ReuseDesugaringStrategy'", + ], + test_class = "com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTest", + deps = [ + ":throwable_extension_test_utility", + "//src/test/java/com/google/devtools/build/android/desugar:mocked_android_os_sdk_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "ConcurrentWeakIdentityHashMapTest", + timeout = "long", # multi-threaded and rely on GC, so it could take long. + srcs = ["ConcurrentWeakIdentityHashMapTest.java"], + test_class = "com.google.devtools.build.android.desugar.runtime.ConcurrentWeakIdentityHashMapTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension", + "//third_party:guava", + "//third_party:guava-testlib", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "UnsignedIntsTest", + srcs = ["UnsignedIntsTest.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:primitives", + "//third_party:junit4", + ], +) + +java_test( + name = "UnsignedLongsTest", + srcs = ["UnsignedLongsTest.java"], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:primitives", + "//third_party:junit4", + ], +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/ConcurrentWeakIdentityHashMapTest.java b/src/test/java/com/google/devtools/build/android/desugar/runtime/ConcurrentWeakIdentityHashMapTest.java new file mode 100644 index 00000000000000..a2e47b006768ee --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/ConcurrentWeakIdentityHashMapTest.java @@ -0,0 +1,260 @@ +// 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.runtime; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.GcFinalization; +import com.google.devtools.build.android.desugar.runtime.ThrowableExtension.ConcurrentWeakIdentityHashMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test for {@link ConcurrentWeakIdentityHashMap}. This test uses multi-threading, and needs GC + * sometime to assert weak references, so it could take long. + */ +@RunWith(JUnit4.class) +public class ConcurrentWeakIdentityHashMapTest { + + private final Random random = new Random(); + + /** + * This method makes sure that after return, all the exceptions in the map should be garbage + * collected. . + */ + private static ConcurrentWeakIdentityHashMap + testConcurrentWeakIdentityHashMapSingleThreadedHelper(CountDownLatch latch) + throws InterruptedException { + ConcurrentWeakIdentityHashMap map = new ConcurrentWeakIdentityHashMap(); + Exception e1 = new ExceptionWithLatch("e1", latch); + assertThat(map.get(e1, false)).isNull(); + assertThat(map.get(e1, true)).isNotNull(); + assertThat(map.get(e1, true)).isEmpty(); + assertThat(map.get(e1, false)).isNotNull(); + + Exception suppressed1 = new ExceptionWithLatch("suppressed1", latch); + map.get(e1, true).add(suppressed1); + assertThat(map.get(e1, true)).containsExactly(suppressed1); + + Exception suppressed2 = new ExceptionWithLatch("suppressed2", latch); + map.get(e1, true).add(suppressed2); + assertThat(map.get(e1, true)).containsExactly(suppressed1, suppressed2); + + assertThat(map.get(suppressed1, false)).isNull(); + assertThat(map.get(suppressed2, false)).isNull(); + assertThat(map.size()).isEqualTo(1); + assertThat(map.get(suppressed1, true)).isNotNull(); + assertThat(map.size()).isEqualTo(2); + assertThat(map.get(suppressed1, true)).isNotNull(); + assertThat(map.size()).isEqualTo(2); + + Exception e2 = new ExceptionWithLatch("e2", latch); + assertThat(map.get(e2, true)).isNotNull(); + Exception e3 = new ExceptionWithLatch("e3", latch); + assertThat(map.get(e3, true)).isNotNull(); + assertThat(map.size()).isEqualTo(4); + return map; + } + + @Test + public void testSingleThreadedUse() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(5); + ConcurrentWeakIdentityHashMap map = + testConcurrentWeakIdentityHashMapSingleThreadedHelper(latch); + for (int i = 0; i < 5; i++) { + map.deleteEmptyKeys(); + GcFinalization.awaitFullGc(); + } + latch.await(); // wait for e1 to be garbage collected. + map.deleteEmptyKeys(); + assertThat(map.size()).isEqualTo(0); + } + + private static Map> createExceptionWithSuppressed( + int numMainExceptions, int numSuppressedPerMain, CountDownLatch latch) { + Map> map = new HashMap<>(); + for (int i = 0; i < numMainExceptions; ++i) { + Exception main = new ExceptionWithLatch("main-" + i, latch); + List suppressedList = new ArrayList<>(); + assertThat(map).doesNotContainKey(main); + map.put(main, suppressedList); + for (int j = 0; j < numSuppressedPerMain; ++j) { + Exception suppressed = new ExceptionWithLatch("suppressed-" + j + "-main-" + i, latch); + suppressedList.add(suppressed); + } + } + return map; + } + + private ConcurrentWeakIdentityHashMap testFunctionalCorrectnessForMultiThreadedUse( + int numMainExceptions, int numSuppressedPerMain, CountDownLatch latch) + throws InterruptedException { + Map> exceptionWithSuppressed = + createExceptionWithSuppressed(numMainExceptions, numSuppressedPerMain, latch); + assertThat(exceptionWithSuppressed).hasSize(numMainExceptions); + List allPairs = + exceptionWithSuppressed.entrySet().stream() + .flatMap( + entry -> entry.getValue().stream().map(value -> new Pair(entry.getKey(), value))) + .collect(Collectors.toList()); + Collections.shuffle(allPairs); + ConcurrentWeakIdentityHashMap map = new ConcurrentWeakIdentityHashMap(); + List workers = + IntStream.range(1, 11) // ten threads. + .mapToObj(i -> new Worker("worker-" + i, map)) + .collect(Collectors.toList()); + + // Assign tasks to workers. + Iterator workIterator = workers.iterator(); + for (Pair pair : allPairs) { + if (!workIterator.hasNext()) { + workIterator = workers.iterator(); + } + assertThat(workIterator.hasNext()).isTrue(); + workIterator.next().exceptionList.add(pair); + } + + // Execute all the workers. + ExecutorService executorService = Executors.newFixedThreadPool(workers.size()); + workers.forEach(executorService::execute); + executorService.shutdown(); + executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); // wait for completion. + exceptionWithSuppressed + .entrySet() + .forEach( + entry -> { + assertThat(map.get(entry.getKey(), false)).isNotNull(); + assertThat(map.get(entry.getKey(), false)) + .containsExactlyElementsIn(exceptionWithSuppressed.get(entry.getKey())); + }); + return map; + } + + private void testMultiThreadedUse(int numMainExceptions, int numSuppressedPerMain) + throws InterruptedException { + CountDownLatch latch = new CountDownLatch(numMainExceptions * numSuppressedPerMain); + ConcurrentWeakIdentityHashMap map = + testFunctionalCorrectnessForMultiThreadedUse( + numMainExceptions, numSuppressedPerMain, latch); + /* + * Calling the following methods multiple times to make sure the keys are garbage collected, + * and their corresponding entries are removed from the map. + */ + map.deleteEmptyKeys(); + GcFinalization.awaitFullGc(); + map.deleteEmptyKeys(); + GcFinalization.awaitFullGc(); + map.deleteEmptyKeys(); + + assertThat(map.size()).isEqualTo(0); + } + + @Test + public void testMultiThreadedUseMedium() throws InterruptedException { + for (int i = 0; i < 10; ++i) { + testMultiThreadedUse(50, 100); + } + } + + @Test + public void testMultiThreadedUseLarge() throws InterruptedException { + for (int i = 0; i < 5; ++i) { + testMultiThreadedUse(100, 100); + } + } + + @Test + public void testMultiThreadedUseSmall() throws InterruptedException { + for (int i = 0; i < 10; ++i) { + testMultiThreadedUse(20, 100); + } + } + + private static class ExceptionWithLatch extends Exception { + private final CountDownLatch latch; + + private ExceptionWithLatch(String message, CountDownLatch latch) { + super(message); + this.latch = latch; + } + + @Override + public String toString() { + return this.getMessage(); + } + + @Override + protected void finalize() throws Throwable { + latch.countDown(); + } + } + + private static class Pair { + final Throwable throwable; + final Throwable suppressed; + + public Pair(Throwable throwable, Throwable suppressed) { + this.throwable = throwable; + this.suppressed = suppressed; + } + } + + private class Worker implements Runnable { + private final ConcurrentWeakIdentityHashMap map; + private final List exceptionList = new ArrayList<>(); + private final String name; + + private Worker(String name, ConcurrentWeakIdentityHashMap map) { + this.name = name; + this.map = map; + } + + public String getName() { + return name; + } + + @Override + public void run() { + Iterator iterator = exceptionList.iterator(); + while (iterator.hasNext()) { + int timeToSleep = random.nextInt(3); + if (random.nextBoolean() && timeToSleep > 0) { + try { + Thread.sleep(timeToSleep); // add randomness to the scheduler. + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + Pair pair = iterator.next(); + List suppressed = map.get(pair.throwable, true); + System.out.printf("add suppressed %s to %s\n", pair.suppressed, pair.throwable); + suppressed.add(pair.suppressed); + } + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/StringConcatsTest.java b/src/test/java/com/google/devtools/build/android/desugar/runtime/StringConcatsTest.java new file mode 100644 index 00000000000000..47bf0a3102c732 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/StringConcatsTest.java @@ -0,0 +1,40 @@ +// Copyright 2019 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.runtime; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link StringConcats}, the utility class for string concatenation desugaring. */ +@RunWith(JUnit4.class) +public final class StringConcatsTest { + + @Test + public void concat_directConcat() { + assertThat(StringConcats.concat(new String[] {"a", "bc"}, "\1\1", new Object[] {})) + .isEqualTo("abc"); + } + + @Test + public void concat_recipeTemplate() { + assertThat( + StringConcats.concat( + new String[] {"a", "bc"}, "\2\1
    \1\2", new Object[] {"
  • ", ""})) + .isEqualTo("
  • a
    bc
    "); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtensionTest.java b/src/test/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtensionTest.java new file mode 100644 index 00000000000000..80a90471eaece7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtensionTest.java @@ -0,0 +1,453 @@ +// 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.runtime; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtension.MimicDesugaringStrategy.SUPPRESSED_PREFIX; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.getTwrStrategyClassNameSpecifiedInSystemProperty; +import static com.google.devtools.build.android.desugar.runtime.ThrowableExtensionTestUtility.isNullStrategy; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +import com.google.devtools.build.android.desugar.runtime.ThrowableExtension.MimicDesugaringStrategy; +import com.google.devtools.build.android.desugar.runtime.ThrowableExtension.NullDesugaringStrategy; +import com.google.devtools.build.android.desugar.runtime.ThrowableExtension.ReuseDesugaringStrategy; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.function.Consumer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test case for {@link ThrowableExtension} */ +@RunWith(JUnit4.class) +public class ThrowableExtensionTest { + + /** + * This test tests the behavior of closing resources via reflection. This is only enabled below + * API 19. So, if the API level is 19 or above, this test will simply skip. + */ + @Test + public void testCloseResourceViaReflection() throws Throwable { + class Resource extends AbstractResource { + protected Resource(boolean exceptionOnClose) { + super(exceptionOnClose); + } + + public void close() throws Exception { + super.internalClose(); + } + } + if (ThrowableExtension.API_LEVEL >= 19) { + return; + } + { + Resource r = new Resource(false); + assertThat(r.isClosed()).isFalse(); + ThrowableExtension.closeResource(null, r); + assertThat(r.isClosed()).isTrue(); + } + { + Resource r = new Resource(true); + assertThat(r.isClosed()).isFalse(); + assertThrows(IOException.class, () -> ThrowableExtension.closeResource(null, r)); + } + { + Resource r = new Resource(false); + assertThat(r.isClosed()).isFalse(); + ThrowableExtension.closeResource(new Exception(), r); + assertThat(r.isClosed()).isTrue(); + } + { + Resource r = new Resource(true); + assertThat(r.isClosed()).isFalse(); + assertThrows(Exception.class, () -> ThrowableExtension.closeResource(new Exception(), r)); + } + } + + /** + * Test the new method closeResources() in the runtime library. + * + *

    The method is introduced to fix b/37167433. + */ + @Test + public void testCloseResource() throws Throwable { + + /** + * A resource implementing the interface AutoCloseable. This interface is only available since + * API 19. + */ + class AutoCloseableResource extends AbstractResource implements AutoCloseable { + + protected AutoCloseableResource(boolean exceptionOnClose) { + super(exceptionOnClose); + } + + @Override + public void close() throws Exception { + internalClose(); + } + } + + /** A resource implementing the interface Closeable. */ + class CloseableResource extends AbstractResource implements Closeable { + + protected CloseableResource(boolean exceptionOnClose) { + super(exceptionOnClose); + } + + @Override + public void close() throws IOException { + internalClose(); + } + } + + { + CloseableResource r = new CloseableResource(false); + assertThat(r.isClosed()).isFalse(); + ThrowableExtension.closeResource(null, r); + assertThat(r.isClosed()).isTrue(); + } + { + CloseableResource r = new CloseableResource(false); + assertThat(r.isClosed()).isFalse(); + Exception suppressor = new Exception(); + ThrowableExtension.closeResource(suppressor, r); + assertThat(r.isClosed()).isTrue(); + assertThat(ThrowableExtension.getSuppressed(suppressor)).isEmpty(); + } + { + CloseableResource r = new CloseableResource(true); + assertThat(r.isClosed()).isFalse(); + assertThrows(IOException.class, () -> ThrowableExtension.closeResource(null, r)); + assertThat(r.isClosed()).isFalse(); + } + { + CloseableResource r = new CloseableResource(true); + assertThat(r.isClosed()).isFalse(); + Exception suppressor = new Exception(); + assertThrows(Exception.class, () -> ThrowableExtension.closeResource(suppressor, r)); + assertThat(r.isClosed()).isFalse(); // Failed to close. + if (!isNullStrategy()) { + assertThat(ThrowableExtension.getSuppressed(suppressor)).hasLength(1); + assertThat(ThrowableExtension.getSuppressed(suppressor)[0].getClass()) + .isEqualTo(IOException.class); + } + } + { + AutoCloseableResource r = new AutoCloseableResource(false); + assertThat(r.isClosed()).isFalse(); + ThrowableExtension.closeResource(null, r); + assertThat(r.isClosed()).isTrue(); + } + { + AutoCloseableResource r = new AutoCloseableResource(false); + assertThat(r.isClosed()).isFalse(); + Exception suppressor = new Exception(); + ThrowableExtension.closeResource(suppressor, r); + assertThat(r.isClosed()).isTrue(); + assertThat(ThrowableExtension.getSuppressed(suppressor)).isEmpty(); + } + { + AutoCloseableResource r = new AutoCloseableResource(true); + assertThat(r.isClosed()).isFalse(); + assertThrows(IOException.class, () -> ThrowableExtension.closeResource(null, r)); + assertThat(r.isClosed()).isFalse(); + } + { + AutoCloseableResource r = new AutoCloseableResource(true); + assertThat(r.isClosed()).isFalse(); + Exception suppressor = new Exception(); + assertThrows(Exception.class, () -> ThrowableExtension.closeResource(suppressor, r)); + assertThat(r.isClosed()).isFalse(); // Failed to close. + if (!isNullStrategy()) { + assertThat(ThrowableExtension.getSuppressed(suppressor)).hasLength(1); + assertThat(ThrowableExtension.getSuppressed(suppressor)[0].getClass()) + .isEqualTo(IOException.class); + } + assertThat(r.isClosed()).isFalse(); + } + } + + /** + * LightweightStackTraceRecorder tracks the calls of various printStackTrace(*), and ensures that + * + *

    suppressed exceptions are printed only once. + */ + @Test + public void testLightweightStackTraceRecorder() throws IOException { + MimicDesugaringStrategy strategy = new MimicDesugaringStrategy(); + ExceptionForTest receiver = new ExceptionForTest(strategy); + FileNotFoundException suppressed = new FileNotFoundException(); + strategy.addSuppressed(receiver, suppressed); + + String trace = printStackTraceStderrToString(() -> strategy.printStackTrace(receiver)); + assertThat(trace).contains(SUPPRESSED_PREFIX); + assertThat(countOccurrences(trace, SUPPRESSED_PREFIX)).isEqualTo(1); + } + + @Test + public void testMimicDesugaringStrategy() throws IOException { + MimicDesugaringStrategy strategy = new MimicDesugaringStrategy(); + IOException receiver = new IOException(); + FileNotFoundException suppressed = new FileNotFoundException(); + strategy.addSuppressed(receiver, suppressed); + + assertThat( + printStackTracePrintStreamToString( + stream -> strategy.printStackTrace(receiver, stream))) + .contains(SUPPRESSED_PREFIX); + + assertThat( + printStackTracePrintWriterToString( + writer -> strategy.printStackTrace(receiver, writer))) + .contains(SUPPRESSED_PREFIX); + + assertThat(printStackTraceStderrToString(() -> strategy.printStackTrace(receiver))) + .contains(SUPPRESSED_PREFIX); + } + + private void testThrowableExtensionWithMimicDesugaringStrategy() throws IOException { + IOException receiver = new IOException(); + FileNotFoundException suppressed = new FileNotFoundException(); + ThrowableExtension.addSuppressed(receiver, suppressed); + + assertThat( + printStackTracePrintStreamToString( + stream -> ThrowableExtension.printStackTrace(receiver, stream))) + .contains(SUPPRESSED_PREFIX); + assertThat( + printStackTracePrintWriterToString( + writer -> ThrowableExtension.printStackTrace(receiver, writer))) + .contains(SUPPRESSED_PREFIX); + assertThat(printStackTraceStderrToString(() -> ThrowableExtension.printStackTrace(receiver))) + .contains(SUPPRESSED_PREFIX); + } + + private interface PrintStackTraceCaller { + void printStackTrace(); + } + + private static String printStackTraceStderrToString(PrintStackTraceCaller caller) + throws IOException { + PrintStream err = System.err; + try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + PrintStream newErr = new PrintStream(stream); + System.setErr(newErr); + caller.printStackTrace(); + newErr.flush(); + return stream.toString(); + } finally { + System.setErr(err); + } + } + + private static String printStackTracePrintStreamToString(Consumer caller) + throws IOException { + try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + PrintStream printStream = new PrintStream(stream); + caller.accept(printStream); + printStream.flush(); + return stream.toString(); + } + } + + private static String printStackTracePrintWriterToString(Consumer caller) + throws IOException { + try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + PrintWriter printWriter = + new PrintWriter(new BufferedWriter(new OutputStreamWriter(stream, UTF_8))); + caller.accept(printWriter); + printWriter.flush(); + return stream.toString(); + } + } + + @Test + public void testNullDesugaringStrategy() throws IOException { + NullDesugaringStrategy strategy = new NullDesugaringStrategy(); + IOException receiver = new IOException(); + FileNotFoundException suppressed = new FileNotFoundException(); + strategy.addSuppressed(receiver, suppressed); + assertThat(strategy.getSuppressed(receiver)).isEmpty(); + + strategy.addSuppressed(receiver, suppressed); + assertThat(strategy.getSuppressed(receiver)).isEmpty(); + + assertThat(printStackTracePrintStreamToString(stream -> receiver.printStackTrace(stream))) + .isEqualTo( + printStackTracePrintStreamToString( + stream -> strategy.printStackTrace(receiver, stream))); + + assertThat(printStackTracePrintWriterToString(receiver::printStackTrace)) + .isEqualTo( + printStackTracePrintWriterToString( + writer -> strategy.printStackTrace(receiver, writer))); + + assertThat(printStackTraceStderrToString(receiver::printStackTrace)) + .isEqualTo(printStackTraceStderrToString(() -> strategy.printStackTrace(receiver))); + } + + private void testThrowableExtensionWithNullDesugaringStrategy() throws IOException { + IOException receiver = new IOException(); + FileNotFoundException suppressed = new FileNotFoundException(); + ThrowableExtension.addSuppressed(receiver, suppressed); + assertThat(ThrowableExtension.getSuppressed(receiver)).isEmpty(); + + ThrowableExtension.addSuppressed(receiver, suppressed); + assertThat(ThrowableExtension.getSuppressed(receiver)).isEmpty(); + + assertThat(printStackTracePrintStreamToString(stream -> receiver.printStackTrace(stream))) + .isEqualTo( + printStackTracePrintStreamToString( + stream -> ThrowableExtension.printStackTrace(receiver, stream))); + assertThat(printStackTracePrintWriterToString(receiver::printStackTrace)) + .isEqualTo( + printStackTracePrintWriterToString( + writer -> ThrowableExtension.printStackTrace(receiver, writer))); + + assertThat(printStackTraceStderrToString(receiver::printStackTrace)) + .isEqualTo( + printStackTraceStderrToString(() -> ThrowableExtension.printStackTrace(receiver))); + } + + @Test + public void testReuseDesugaringStrategy() throws IOException { + ReuseDesugaringStrategy strategy = new ReuseDesugaringStrategy(); + IOException receiver = new IOException(); + FileNotFoundException suppressed = new FileNotFoundException(); + strategy.addSuppressed(receiver, suppressed); + assertThat(strategy.getSuppressed(receiver)) + .asList() + .containsExactly((Object[]) receiver.getSuppressed()); + + assertThat(printStackTracePrintStreamToString(stream -> receiver.printStackTrace(stream))) + .isEqualTo( + printStackTracePrintStreamToString( + stream -> strategy.printStackTrace(receiver, stream))); + + assertThat(printStackTracePrintWriterToString(receiver::printStackTrace)) + .isEqualTo( + printStackTracePrintWriterToString( + writer -> strategy.printStackTrace(receiver, writer))); + assertThat(printStackTraceStderrToString(receiver::printStackTrace)) + .isEqualTo(printStackTraceStderrToString(() -> strategy.printStackTrace(receiver))); + } + + private void testThrowableExtensionWithReuseDesugaringStrategy() throws IOException { + IOException receiver = new IOException(); + FileNotFoundException suppressed = new FileNotFoundException(); + ThrowableExtension.addSuppressed(receiver, suppressed); + assertThat(ThrowableExtension.getSuppressed(receiver)) + .asList() + .containsExactly((Object[]) receiver.getSuppressed()); + + assertThat(printStackTracePrintStreamToString(receiver::printStackTrace)) + .isEqualTo( + printStackTracePrintStreamToString( + stream -> ThrowableExtension.printStackTrace(receiver, stream))); + + assertThat(printStackTracePrintWriterToString(receiver::printStackTrace)) + .isEqualTo( + printStackTracePrintWriterToString( + writer -> ThrowableExtension.printStackTrace(receiver, writer))); + + assertThat(printStackTraceStderrToString(receiver::printStackTrace)) + .isEqualTo( + printStackTraceStderrToString(() -> ThrowableExtension.printStackTrace(receiver))); + } + + /** This class */ + private static class ExceptionForTest extends Exception { + + private final MimicDesugaringStrategy strategy; + + public ExceptionForTest(MimicDesugaringStrategy strategy) { + this.strategy = strategy; + } + + @Override + public void printStackTrace() { + this.printStackTrace(System.err); + } + + /** + * This method should call this.printStackTrace(PrintWriter) directly. I deliberately change it + * to strategy.printStackTrace(Throwable, PrintWriter) to simulate the behavior of Desguar, that + * is, the direct call is intercepted and redirected to ThrowableExtension. + */ + @Override + public void printStackTrace(PrintStream s) { + this.strategy.printStackTrace( + this, new PrintWriter(new BufferedWriter(new OutputStreamWriter(s, UTF_8)))); + } + } + + @Test + public void testStrategySelection() throws ClassNotFoundException, IOException { + String expectedStrategyClassName = getTwrStrategyClassNameSpecifiedInSystemProperty(); + assertThat(expectedStrategyClassName).isNotEmpty(); + assertThat(ThrowableExtension.STRATEGY.getClass().getName()) + .isEqualTo(expectedStrategyClassName); + + Class expectedStrategyClass = Class.forName(expectedStrategyClassName); + if (expectedStrategyClass.equals(ReuseDesugaringStrategy.class)) { + testThrowableExtensionWithReuseDesugaringStrategy(); + } else if (expectedStrategyClass.equals(MimicDesugaringStrategy.class)) { + testThrowableExtensionWithMimicDesugaringStrategy(); + } else if (expectedStrategyClass.equals(NullDesugaringStrategy.class)) { + testThrowableExtensionWithNullDesugaringStrategy(); + } else { + fail("unrecognized expected strategy class " + expectedStrategyClassName); + } + } + + private static int countOccurrences(String string, String substring) { + int i = 0; + int count = 0; + while ((i = string.indexOf(substring, i)) >= 0) { + ++count; + i = i + string.length(); + } + return count; + } + + /** A mocked closeable class, which we can query the closedness. */ + private abstract static class AbstractResource { + private final boolean exceptionOnClose; + private boolean closed; + + protected AbstractResource(boolean exceptionOnClose) { + this.exceptionOnClose = exceptionOnClose; + } + + boolean isClosed() { + return closed; + } + + void internalClose() throws IOException { + if (exceptionOnClose) { + throw new IOException("intended exception"); + } + closed = true; + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtensionTestUtility.java b/src/test/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtensionTestUtility.java new file mode 100644 index 00000000000000..489bd7a7dc1826 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/ThrowableExtensionTestUtility.java @@ -0,0 +1,73 @@ +// 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.runtime; + +import static com.google.common.truth.Truth.assertThat; + +import java.lang.reflect.Method; + +/** + * A utility class for testing ThrowableExtension. It uses reflection to get the strategy name, so + * as to avoid dependency on the runtime library. This is beneficial, because we can test whether + * the runtime library is on the classpath. + */ +public class ThrowableExtensionTestUtility { + + private static final String SYSTEM_PROPERTY_EXPECTED_STRATEGY = "expected.strategy"; + + public static String getTwrStrategyClassNameSpecifiedInSystemProperty() { + String className = unquote(System.getProperty(SYSTEM_PROPERTY_EXPECTED_STRATEGY)); + assertThat(className).isNotEmpty(); + return className; + } + + private static final String THROWABLE_EXTENSION_CLASS_NAME = + "com.google.devtools.build.android.desugar.runtime.ThrowableExtension"; + + private static boolean isStrategyOfClass(String className) { + return getStrategyClassName().equals(className); + } + + public static String getStrategyClassName() { + try { + Class klass = Class.forName(THROWABLE_EXTENSION_CLASS_NAME); + Method method = klass.getMethod("getStrategy"); + Object strategy = method.invoke(null); + return strategy.getClass().getName(); + } catch (Throwable e) { + throw new AssertionError(e); + } + } + + public static boolean isMimicStrategy() { + return isStrategyOfClass(THROWABLE_EXTENSION_CLASS_NAME + "$MimicDesugaringStrategy"); + } + + public static boolean isNullStrategy() { + return isStrategyOfClass(THROWABLE_EXTENSION_CLASS_NAME + "$NullDesugaringStrategy"); + } + + public static boolean isReuseStrategy() { + return isStrategyOfClass(THROWABLE_EXTENSION_CLASS_NAME + "$ReuseDesugaringStrategy"); + } + + private static String unquote(String s) { + if (s.startsWith("'") || s.startsWith("\"")) { + assertThat(s).endsWith(s.substring(0, 1)); + return s.substring(1, s.length() - 1); + } else { + return s; + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedIntsTest.java b/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedIntsTest.java new file mode 100644 index 00000000000000..51f696733f695a --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedIntsTest.java @@ -0,0 +1,113 @@ +// Copyright 2020 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.runtime; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link UnsignedInts}. */ +@RunWith(JUnit4.class) +public class UnsignedIntsTest { + private static final long[] UNSIGNED_INTS = { + 0L, + 1L, + 2L, + 3L, + 0x12345678L, + 0x5a4316b8L, + 0x6cf78a4bL, + 0xff1a618bL, + 0xfffffffdL, + 0xfffffffeL, + 0xffffffffL + }; + + @Test + public void testToLong() { + for (long a : UNSIGNED_INTS) { + assertEquals(a, UnsignedInts.toLong((int) a)); + } + } + + @Test + public void testCompare() { + for (long a : UNSIGNED_INTS) { + for (long b : UNSIGNED_INTS) { + int cmpAsLongs = Long.compare(a, b); + int cmpAsUInt = UnsignedInts.compare((int) a, (int) b); + assertEquals(Integer.signum(cmpAsLongs), Integer.signum(cmpAsUInt)); + } + } + } + + @Test + public void testDivide() { + for (long a : UNSIGNED_INTS) { + for (long b : UNSIGNED_INTS) { + try { + assertEquals((int) (a / b), UnsignedInts.divide((int) a, (int) b)); + assertFalse(b == 0); + } catch (ArithmeticException e) { + assertEquals(0, b); + } + } + } + } + + @Test + public void testRemainder() { + for (long a : UNSIGNED_INTS) { + for (long b : UNSIGNED_INTS) { + try { + assertEquals((int) (a % b), UnsignedInts.remainder((int) a, (int) b)); + assertFalse(b == 0); + } catch (ArithmeticException e) { + assertEquals(0, b); + } + } + } + } + + @Test + public void testDivideRemainderEuclideanProperty() { + // Use a seed so that the test is deterministic: + Random r = new Random(0L); + for (int i = 0; i < 1000000; i++) { + int dividend = r.nextInt(); + int divisor = r.nextInt(); + // Test that the Euclidean property is preserved: + assertEquals( + 0, + dividend + - (divisor * UnsignedInts.divide(dividend, divisor) + + UnsignedInts.remainder(dividend, divisor))); + } + } + + @Test + public void testToString() { + int[] bases = {2, 5, 7, 8, 10, 16}; + for (long a : UNSIGNED_INTS) { + for (int base : bases) { + assertEquals(UnsignedInts.toString((int) a, base), Long.toString(a, base)); + } + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongsTest.java b/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongsTest.java new file mode 100644 index 00000000000000..235a1fdc815314 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/runtime/UnsignedLongsTest.java @@ -0,0 +1,111 @@ +// Copyright 2019 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.runtime; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.math.BigInteger; +import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link UnsignedLongs}. */ +@RunWith(JUnit4.class) +public class UnsignedLongsTest { + + @Test + public void testCompare() { + // max value + assertTrue(UnsignedLongs.compare(0, 0xffffffffffffffffL) < 0); + assertTrue(UnsignedLongs.compare(0xffffffffffffffffL, 0) > 0); + + // both with high bit set + assertTrue(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0xffffffffffffffffL) < 0); + assertTrue(UnsignedLongs.compare(0xffffffffffffffffL, 0xff1a618b7f65ea12L) > 0); + + // one with high bit set + assertTrue(UnsignedLongs.compare(0x5a4316b8c153ac4dL, 0xff1a618b7f65ea12L) < 0); + assertTrue(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0x5a4316b8c153ac4dL) > 0); + + // neither with high bit set + assertTrue(UnsignedLongs.compare(0x5a4316b8c153ac4dL, 0x6cf78a4b139a4e2aL) < 0); + assertTrue(UnsignedLongs.compare(0x6cf78a4b139a4e2aL, 0x5a4316b8c153ac4dL) > 0); + + // same value + assertTrue(UnsignedLongs.compare(0xff1a618b7f65ea12L, 0xff1a618b7f65ea12L) == 0); + } + + @Test + public void testDivide() { + assertEquals(2, UnsignedLongs.divideUnsigned(14, 5)); + assertEquals(0, UnsignedLongs.divideUnsigned(0, 50)); + assertEquals(1, UnsignedLongs.divideUnsigned(0xfffffffffffffffeL, 0xfffffffffffffffdL)); + assertEquals(0, UnsignedLongs.divideUnsigned(0xfffffffffffffffdL, 0xfffffffffffffffeL)); + assertEquals(281479271743488L, UnsignedLongs.divideUnsigned(0xfffffffffffffffeL, 65535)); + assertEquals(0x7fffffffffffffffL, UnsignedLongs.divideUnsigned(0xfffffffffffffffeL, 2)); + assertEquals(3689348814741910322L, UnsignedLongs.divideUnsigned(0xfffffffffffffffeL, 5)); + } + + @Test + public void testRemainder() { + assertEquals(4, UnsignedLongs.remainderUnsigned(14, 5)); + assertEquals(0, UnsignedLongs.remainderUnsigned(0, 50)); + assertEquals(1, UnsignedLongs.remainderUnsigned(0xfffffffffffffffeL, 0xfffffffffffffffdL)); + assertEquals( + 0xfffffffffffffffdL, + UnsignedLongs.remainderUnsigned(0xfffffffffffffffdL, 0xfffffffffffffffeL)); + assertEquals(65534L, UnsignedLongs.remainderUnsigned(0xfffffffffffffffeL, 65535)); + assertEquals(0, UnsignedLongs.remainderUnsigned(0xfffffffffffffffeL, 2)); + assertEquals(4, UnsignedLongs.remainderUnsigned(0xfffffffffffffffeL, 5)); + } + + @Test + public void testDivideRemainderEuclideanProperty() { + // Use a seed so that the test is deterministic: + Random r = new Random(0L); + for (int i = 0; i < 1000000; i++) { + long dividend = r.nextLong(); + long divisor = r.nextLong(); + // Test that the Euclidean property is preserved: + assertEquals( + 0, + dividend + - (divisor * UnsignedLongs.divideUnsigned(dividend, divisor) + + UnsignedLongs.remainderUnsigned(dividend, divisor))); + } + } + + @Test + public void testToString() { + String[] tests = { + "0", + "ffffffffffffffff", + "7fffffffffffffff", + "ff1a618b7f65ea12", + "5a4316b8c153ac4d", + "6cf78a4b139a4e2a" + }; + int[] bases = {2, 5, 7, 8, 10, 16}; + for (int base : bases) { + for (String x : tests) { + BigInteger xValue = new BigInteger(x, 16); + long xLong = xValue.longValue(); // signed + assertEquals(xValue.toString(base), UnsignedLongs.toString(xLong, base)); + } + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/simple_instance_method_reference_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/simple_instance_method_reference_disassembled_golden.txt new file mode 100644 index 00000000000000..0848a09c9394fa --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/simple_instance_method_reference_disassembled_golden.txt @@ -0,0 +1,22 @@ +Compiled from "MethodReferenceSuperclass.java" +final class com.google.devtools.build.android.desugar.testdata.MethodReferenceSuperclass$$Lambda$0 implements java.util.function.Predicate { + private final com.google.devtools.build.android.desugar.testdata.MethodReferenceSuperclass arg$1; + + com.google.devtools.build.android.desugar.testdata.MethodReferenceSuperclass$$Lambda$0(com.google.devtools.build.android.desugar.testdata.MethodReferenceSuperclass); + Code: + 0: aload_0 + 1: invokespecial #14 // Method java/lang/Object."":()V + 4: aload_0 + 5: aload_1 + 6: putfield #16 // Field arg$1:Lcom/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass; + 9: return + + public boolean test(java.lang.Object); + Code: + 0: aload_0 + 1: getfield #16 // Field arg$1:Lcom/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass; + 4: aload_1 + 5: checkcast #20 // class java/lang/String + 8: invokevirtual #26 // Method com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.bridge$lambda$0$MethodReferenceSuperclass:(Ljava/lang/String;)Z + 11: ireturn +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/stateless_lambda_disassembled_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/stateless_lambda_disassembled_golden.txt new file mode 100644 index 00000000000000..e1f3bcb7b899be --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/stateless_lambda_disassembled_golden.txt @@ -0,0 +1,25 @@ +Compiled from "Lambda.java" +final class com.google.devtools.build.android.desugar.testdata.Lambda$$Lambda$0 implements java.util.function.Predicate { + static final java.util.function.Predicate $instance; + + private com.google.devtools.build.android.desugar.testdata.Lambda$$Lambda$0(); + Code: + 0: aload_0 + 1: invokespecial #13 // Method java/lang/Object."":()V + 4: return + + public boolean test(java.lang.Object); + Code: + 0: aload_1 + 1: checkcast #17 // class java/lang/String + 4: invokestatic #23 // Method com/google/devtools/build/android/desugar/testdata/Lambda.lambda$as$0$Lambda:(Ljava/lang/String;)Z + 7: ireturn + + static {}; + Code: + 0: new #2 // class com/google/devtools/build/android/desugar/testdata/Lambda$$Lambda$0 + 3: dup + 4: invokespecial #25 // Method "":()V + 7: putstatic #27 // Field $instance:Ljava/util/function/Predicate; + 10: return +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/static_initializer_of_functional_interface_should_not_execute.sh b/src/test/java/com/google/devtools/build/android/desugar/static_initializer_of_functional_interface_should_not_execute.sh new file mode 100755 index 00000000000000..32b4c5a6b1fb59 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/static_initializer_of_functional_interface_should_not_execute.sh @@ -0,0 +1,22 @@ +#!/bin/bash -e +# +# 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. + +# Test whether Desugar runs static initializers of interfaces. +if grep "THIS STRING IS NOT EXPECTED TO APPEAR IN THE OUTPUT OF DESUGAR!!!" "${1}" ; then + exit 1 +else + exit 0 +fi diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/CaptureLambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/CaptureLambda.java new file mode 100644 index 00000000000000..91fd1ac3b973f0 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/CaptureLambda.java @@ -0,0 +1,30 @@ +// 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.testdata; + +import java.util.List; +import java.util.stream.Collectors; + +public class CaptureLambda { + + private final List names; + + public CaptureLambda(List names) { + this.names = names; + } + + public List prefixed(String prefix) { + return names.stream().filter(n -> n.startsWith(prefix)).collect(Collectors.toList()); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare.java new file mode 100644 index 00000000000000..1026437b9edffa --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare.java @@ -0,0 +1,51 @@ +// 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.testdata; + +/** This class calls Long.compare(long, long) */ +public class ClassCallingLongCompare { + + public static int compareLongByCallingLong_compare(long a, long b) { + return Long.compare(a, b); + } + + public static String compareLongByCallingLong_compare2(long a, long b) { + if (Long.compare(a, b) == 0) { + return "e"; + } + if (Long.compare(a, b) > 0) { + return "g"; + } + if (Long.compare(a, b) < 0) { + return "l"; + } + throw new AssertionError("unreachable"); + } + + public static int compareLongWithLambda(long a, long b) { + return internalCompare(a, b, (long l1, long l2) -> Long.compare(l1, l2)); + } + + public static int compareLongWithMethodReference(long a, long b) { + return internalCompare(a, b, Long::compare); + } + + private static interface LongCmpFunc { + int compare(long a, long b); + } + + private static int internalCompare(long a, long b, LongCmpFunc func) { + return func.compare(a, b); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull.java new file mode 100644 index 00000000000000..3823f1d774e777 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull.java @@ -0,0 +1,53 @@ +// 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.testdata; + +import java.util.Objects; +import java.util.function.IntSupplier; + +/** This class is for the testing of desugaring calls to Objects.requireNonNull(Object o...) */ +public class ClassCallingRequireNonNull { + + public static int getStringLengthWithMethodReference(String s) { + return toInt(s::length); + } + + public static int toInt(IntSupplier function) { + return function.getAsInt(); + } + + public static int getStringLengthWithLambdaAndExplicitCallToRequireNonNull(final String s) { + return toInt(() -> Objects.requireNonNull(s).length()); + } + + public static char getFirstCharVersionOne(String string) { + Objects.requireNonNull(string); + return string.charAt(0); + } + + public static char getFirstCharVersionTwo(String string) { + string = Objects.requireNonNull(string); + return string.charAt(0); + } + + public static char callRequireNonNullWithArgumentString(String string) { + string = Objects.requireNonNull(string, "the string should not be null"); + return string.charAt(0); + } + + public static char callRequireNonNullWithArgumentSupplier(String string) { + string = Objects.requireNonNull(string, () -> "the string should not be null"); + return string.charAt(0); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources.java new file mode 100644 index 00000000000000..0c37e284be6bfe --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources.java @@ -0,0 +1,123 @@ +// 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.testdata; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; + +/** + * This is a test subject for {@link + * com.google.devtools.build.android.desugar.TryWithResourcesRewriter} + */ +public class ClassUsingTryWithResources { + + /** + * A simple resource, which always throws an exception when being closed. + * + *

    Note that we need to implement java.io.Closeable instead of java.lang.AutoCloseable, because + * AutoCloseable is not available below API 19 + * + *

    java9 will emit $closeResource(Throwable, AutoCloseable) for the following class. + */ + 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()."); + } + } + + /** A resource inheriting the close() method from its parent. */ + public static class InheritanceResource extends SimpleResource {} + + /** This method will always throw {@link java.lang.Exception}. */ + public static void simpleTryWithResources() throws Exception { + // Throwable.addSuppressed(Throwable) should be called in the following block. + try (SimpleResource resource = new SimpleResource()) { + resource.call(true); + } + } + + /** A simple resource type for testing try-with-resources with multiple resources. */ + public static class SimpleCloseable implements Closeable { + + /** This method tests a method with method reference as resources. */ + public void multipleTryWithResources() throws Exception { + try (Closeable resource1 = new SimpleResource(); + Closeable resource2 = this::close; + Closeable resource3 = new SimpleResource()) {} + } + + @Override + public void close() throws IOException { + throw new IOException("exception in close()."); + } + } + + public static void multipleTryWithResources() throws Exception { + SimpleCloseable resource = new SimpleCloseable(); + resource.multipleTryWithResources(); + } + + /** + * This method useds {@link InheritanceResource}, which inherits all methods from {@link + * SimpleResource}. + */ + public static void inheritanceTryWithResources() throws Exception { + // Throwable.addSuppressed(Throwable) should be called in the following block. + try (InheritanceResource resource = new InheritanceResource()) { + resource.call(true); + } + } + + public static Throwable[] checkSuppressedExceptions(boolean throwException) { + // Throwable.addSuppressed(Throwable) should be called in the following block. + try (SimpleResource resource = new SimpleResource()) { + resource.call(throwException); + } catch (Exception e) { + return e.getSuppressed(); // getSuppressed() is called. + } + return new Throwable[0]; + } + + public static String printStackTraceOfCaughtException() { + try { + simpleTryWithResources(); + } catch (Exception e) { + PrintStream err = System.err; + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + System.setErr(new PrintStream(stream, true, "utf-8")); + e.printStackTrace(); + } catch (UnsupportedEncodingException e1) { + throw new AssertionError(e1); + } finally { + System.setErr(err); + } + return new String(stream.toByteArray(), UTF_8); + } + return ""; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/ConcreteFunction.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/ConcreteFunction.java new file mode 100644 index 00000000000000..15f6a36c1c232c --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/ConcreteFunction.java @@ -0,0 +1,56 @@ +// 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.testdata; + +import com.google.devtools.build.android.desugar.testdata.separate.SeparateInterface; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ConcreteFunction implements SpecializedFunction { + @Override + public Long apply(String input) { + return Long.valueOf(input); + } + + // SpecializedParser makes it so we have to search multiple extended interfaces for bridge methods + // when desugaring the lambda returned by this method. + public static SpecializedParser toInt() { + return s -> Integer.valueOf(s); + } + + public static SeparateInterface isInt() { + return l -> Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE; + } + + public static List parseAll( + List in, SpecializedFunction parser) { + return in.stream().map(parser).collect(Collectors.toList()); + } + + public static List doFilter(List in, SeparateInterface filter) { + return in.stream().filter(filter).collect(Collectors.toList()); + } + + interface Parser extends Function { + @Override + public T apply(String in); + } + + public interface SpecializedParser + extends SpecializedFunction, Parser { + @Override + public T apply(String in); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/ConstructorReference.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/ConstructorReference.java new file mode 100644 index 00000000000000..03af2a3ea83187 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/ConstructorReference.java @@ -0,0 +1,56 @@ +// 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.testdata; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class ConstructorReference { + + private final List names; + + private ConstructorReference(String name) { + names = new ArrayList<>(1); + names.add(name); + } + + public ConstructorReference(List names) { + this.names = names; + } + + public List toInt() { + return names.stream().map(Integer::new).collect(Collectors.toList()); + } + + public static Function singleton() { + return ConstructorReference::new; + } + + public static Supplier emptyThroughJavacGeneratedBridge() { + // Because Empty is private in another (inner) class, Javac seems to generate a lambda body + // method in this case that calls the Empty(SentinalType) bridge constructor Javac generates. + return Empty::new; + } + + private static class Empty extends ConstructorReference { + + private Empty() { + super(new ArrayList(0)); + throw new RuntimeException("got it!"); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/GuavaLambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/GuavaLambda.java new file mode 100644 index 00000000000000..4974d2561b5f57 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/GuavaLambda.java @@ -0,0 +1,31 @@ +// 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.testdata; + +import static com.google.common.collect.Iterables.filter; + +import java.util.List; + +public class GuavaLambda { + + private final List names; + + public GuavaLambda(List names) { + this.names = names; + } + + public Iterable as() { + return filter(names, n -> n.startsWith("A")); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/InnerClassLambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/InnerClassLambda.java new file mode 100644 index 00000000000000..5230dce511d5ae --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/InnerClassLambda.java @@ -0,0 +1,50 @@ +// 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.testdata; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class InnerClassLambda { + + protected final List reference; + + public InnerClassLambda(List names) { + this.reference = names; + } + + /** + * Uses a lambda that refers to a method parameter across 2 nested anonymous inner classes as well + * as a field in the outer scope, the former being relatively unusual as it causes javac to emit 2 + * getfields to pass the captured parameter directly to the generated lambda class, covering an + * unusual branch in how we rewrite invokedynamics. + */ + public Function, Callable>> prefixFilter(String prefix) { + return new Function, Callable>>() { + @Override + public Callable> apply(List input) { + return new Callable>() { + @Override + public List call() throws Exception { + return input.stream() + .filter(n -> n.startsWith(prefix) && reference.contains(n)) + .collect(Collectors.toList()); + } + }; + } + }; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda.java new file mode 100644 index 00000000000000..e400d1ee7a9420 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda.java @@ -0,0 +1,26 @@ +// 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.testdata; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.stream.Collectors; + +public interface InterfaceWithLambda { + String ZERO = String.valueOf(0); + List DIGITS = + ImmutableList.of(0, 1).stream() + .map(i -> i == 0 ? ZERO : String.valueOf(i)) + .collect(Collectors.toList()); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/Lambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/Lambda.java new file mode 100644 index 00000000000000..cd5e559903aa58 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/Lambda.java @@ -0,0 +1,57 @@ +// 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.testdata; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class Lambda { + + private final List names; + + public Lambda(List names) { + this.names = names; + } + + public List as() { + return names.stream().filter(n -> n.startsWith("A")).collect(Collectors.toList()); + } + + public static Callable hello() { + return (Callable & java.util.RandomAccess) () -> "hello"; + } + + public static Function> mult(int x) { + return new Function>() { + @Override + public Callable apply(Integer y) { + return () -> (long) x * (long) y; + } + }; + } + + /** + * Test method for b/62456849. This method will first be converted to a synthetic method by {@link + * com.google.devtools.build.android.desugar.Bug62456849TestDataGenerator}, and then Desugar + * should keep it in this class without desugaring it (such as renaming). + * + *

    Please ignore the lint error on the method name. The method name is intentionally chosen to + * trigger a bug in Desugar. + */ + public static int lambda$mult$0() { + return 0; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/LambdaInOverride.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/LambdaInOverride.java new file mode 100644 index 00000000000000..8f533a3ac28c5a --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/LambdaInOverride.java @@ -0,0 +1,33 @@ +// 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.testdata; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Test class carefully constructed so javac emits a lambda body method called lambda$filter$0, + * which is exactly the name used for the lambda body method generated by javac for the superclass. + */ +public class LambdaInOverride extends OuterReferenceLambda { + public LambdaInOverride(List names) { + super(names); + } + + public List filter(List names) { + return super.filter(names).stream() + .filter(n -> !reference.contains(n)) + .collect(Collectors.toList()); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReference.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReference.java new file mode 100644 index 00000000000000..a293ceab030e53 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReference.java @@ -0,0 +1,88 @@ +// 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.testdata; + +import com.google.devtools.build.android.desugar.testdata.separate.SeparateBaseClass; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class MethodReference extends SeparateBaseClass { + + private final List names; + + public MethodReference(List names) { + super(names); + this.names = names; + } + + // Class method reference + public void appendAll(StringBuilder dest) { + names.stream().forEach(dest::append); + } + + // Interface method reference (regression test for b/33304582) + public List transform(Transformer transformer) { + return names.stream().map(transformer::transform).collect(Collectors.toList()); + } + + // Private method reference (regression test for b/33378312) + public List some() { + return names.stream().filter(MethodReference::startsWithS).collect(Collectors.toList()); + } + + // Protected method reference in a base class of another package (regression test for b/33378312) + public List intersect(List other) { + return other.stream().filter(this::contains).collect(Collectors.toList()); + } + + // Contains the same method reference as intersect + public List onlyIn(List other) { + Predicate p = this::contains; + return other.stream().filter(p.negate()).collect(Collectors.toList()); + } + + // Private method reference to an instance method that throws (regression test for b/33378312) + public Callable stringer() { + return this::throwing; + } + + /** Returns a method reference derived from an expression (object.toString()). */ + public static Function stringChars(Object object) { + return (object == null ? "" : object.toString())::charAt; + } + + /** Returns a method reference derived from a field */ + public Predicate toPredicate() { + return names::contains; + } + + private static boolean startsWithS(String input) { + return input.startsWith("S"); + } + + private String throwing() throws Exception { + StringBuilder msg = new StringBuilder(); + appendAll(msg); + throw new IOException(msg.toString()); + } + + /** Interface to create a method reference for in {@link #transform}. */ + public interface Transformer { + T transform(T input); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass.java new file mode 100644 index 00000000000000..fb1c49ece8c2c9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass.java @@ -0,0 +1,37 @@ +// 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.testdata; + +import java.util.List; +import java.util.stream.Collectors; + +public class MethodReferenceInSubclass extends MethodReferenceSuperclass { + + public MethodReferenceInSubclass(List names) { + super(names); + } + + // Private method reference in subclass that causes a bridge method with the same signature as in + // a superclass in the same package (regression test for b/36201257). Both superclass and this + // class need a method reference to a private *instance* method with the same signature, and they + // should each only one method reference and no lambdas so any class-local counter matches, for + // this class to serve as a repro for b/36201257. + public List containsE() { + return names.stream().filter(this::containsE).collect(Collectors.toList()); + } + + private boolean containsE(String input) { + return input.contains("e"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.java new file mode 100644 index 00000000000000..ac7aa4ddb4b5b2 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.java @@ -0,0 +1,36 @@ +// 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.testdata; + +import java.util.List; +import java.util.stream.Collectors; + +public class MethodReferenceSuperclass { + + protected final List names; + + public MethodReferenceSuperclass(List names) { + this.names = names; + } + + // Method reference that causes a simple bridge method because the referenced method is private. + // We want to make sure that bridge methods generated in subclasses don't clobber this one. + public List startsWithL() { + return names.stream().filter(this::startsWithL).collect(Collectors.toList()); + } + + private boolean startsWithL(String input) { + return input.startsWith("L"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda.java new file mode 100644 index 00000000000000..8695d1022b1878 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda.java @@ -0,0 +1,30 @@ +// 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.testdata; + +import java.util.List; +import java.util.stream.Collectors; + +public class OuterReferenceLambda { + + protected final List reference; + + public OuterReferenceLambda(List names) { + this.reference = names; + } + + public List filter(List names) { + return names.stream().filter(n -> reference.contains(n)).collect(Collectors.toList()); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/SpecializedFunction.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/SpecializedFunction.java new file mode 100644 index 00000000000000..18e005d6c21da0 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/SpecializedFunction.java @@ -0,0 +1,23 @@ +// 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.testdata; + +import java.util.function.Function; + +public interface SpecializedFunction extends Function { + Integer DO_NOT_COPY_INTO_LAMBDA_CLASSES = Integer.valueOf(42); + + @Override + public T apply(S in); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/b68049457/StaticInterfaceMethod.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/b68049457/StaticInterfaceMethod.java new file mode 100644 index 00000000000000..7815a736571456 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/b68049457/StaticInterfaceMethod.java @@ -0,0 +1,21 @@ +// 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.testdata.b68049457; + +/** Interface declaring a static method for regression test for b/68049457. */ +public interface StaticInterfaceMethod { + static String never() { + throw new IllegalStateException("can't get here"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/b68049457/StaticInterfaceMethodCaller.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/b68049457/StaticInterfaceMethodCaller.java new file mode 100644 index 00000000000000..f961d967503164 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/b68049457/StaticInterfaceMethodCaller.java @@ -0,0 +1,21 @@ +// 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.testdata.b68049457; + +/** Class calling static interface method for regression test for b/68049457. */ +public class StaticInterfaceMethodCaller { + public String callIt() { + return StaticInterfaceMethod.never(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/core_library/java/lang/AutoboxedTypes.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/core_library/java/lang/AutoboxedTypes.java new file mode 100644 index 00000000000000..bdf298cce12f51 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/core_library/java/lang/AutoboxedTypes.java @@ -0,0 +1,36 @@ +// 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. + +// This test class is in the java.lang namespace to trigger the hardcoded JVM restrictions that +// desugar --core_library works around +package java.lang; + +/** + * This class will be desugared with --core_library and then functionally tested by {@code + * DesugarCoreLibraryFunctionalTest} + */ +public class AutoboxedTypes { + /** + * Dummy functional interface for autoboxedTypeLambda to return without introducing a dependency + * on any other java.* classes. + */ + @FunctionalInterface + public interface Lambda { + String charAt(String s); + } + + public static Lambda autoboxedTypeLambda(Integer i) { + return n -> n.substring(i, i + 1); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/core_library/test/util/TestClassForStackMapFrame.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/core_library/test/util/TestClassForStackMapFrame.java new file mode 100644 index 00000000000000..c39d7bc3bfed16 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/core_library/test/util/TestClassForStackMapFrame.java @@ -0,0 +1,56 @@ +// 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 test.util; + +/** Test input for b/36654936 */ +public class TestClassForStackMapFrame { + + /** + * This method caused cl/152199391 to fail due to stack map frame corruption. So it is to make + * sure the desugared version of this class still has correct stack map frames. + */ + public String joinIntegers(int integers) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < integers; i++) { + if (i > 0) { + builder.append(","); + } + builder.append(i); + builder.append('='); + Object value = i % 2 == 0 ? "Even" : "Odd"; + if (i % 2 == 0) { + builder.append(value); + } else { + builder.append(value); + } + } + return builder.toString(); + } + + /** + * This method triggers ASM bug 317785 . + * + * @return 20 + */ + public static int testInputForAsmBug317785() { + Integer x = 0; + for (int i = 0; i < 10; ++i) { + x++; + } + for (int i = 0; i < 10; ++i) { + x++; + } + return x; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/AnnotationsOfDefaultMethodsShouldBeKept.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/AnnotationsOfDefaultMethodsShouldBeKept.java new file mode 100644 index 00000000000000..1747a37008cee2 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/AnnotationsOfDefaultMethodsShouldBeKept.java @@ -0,0 +1,41 @@ +// 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.testdata.java8; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** Test for b/38302860. The annotations of default methods should be kept after desugaring. */ +public class AnnotationsOfDefaultMethodsShouldBeKept { + + /** + * An interface, that has annotation, annotated abstract methods, and annotated default methods. + * After desugaring, all these annotations should remain in the interface. + */ + @SomeAnnotation(1) + public interface AnnotatedInterface { + + @SomeAnnotation(2) + void annotatedAbstractMethod(); + + @SomeAnnotation(3) + default void annotatedDefaultMethod() {} + } + + /** A simple annotation, used for testing. */ + @Retention(value = RetentionPolicy.RUNTIME) + public @interface SomeAnnotation { + int value(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/ConcreteDefaultInterfaceWithLambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/ConcreteDefaultInterfaceWithLambda.java new file mode 100644 index 00000000000000..16daadc2376ea9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/ConcreteDefaultInterfaceWithLambda.java @@ -0,0 +1,29 @@ +// 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.testdata.java8; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.stream.Collectors; + +public class ConcreteDefaultInterfaceWithLambda implements DefaultInterfaceWithLambda { + static final String ONE = String.valueOf(1); + + @Override + public List digits() { + return ImmutableList.of(0, 2).stream() + .map(i -> i == 0 ? ONE : String.valueOf(i)) + .collect(Collectors.toList()); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/ConcreteOverridesDefaultWithLambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/ConcreteOverridesDefaultWithLambda.java new file mode 100644 index 00000000000000..062ecc6bf26327 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/ConcreteOverridesDefaultWithLambda.java @@ -0,0 +1,37 @@ +// 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.testdata.java8; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.stream.Collectors; + +public class ConcreteOverridesDefaultWithLambda implements DefaultInterfaceWithLambda { + static final String TWO = String.valueOf(2); + static final String THREE = String.valueOf(3); + + @Override + public List defaultWithLambda() { + return ImmutableList.of(0, 3).stream() + .map(i -> i == 0 ? TWO : String.valueOf(i)) + .collect(Collectors.toList()); + } + + @Override + public List digits() { + return ImmutableList.of(0, 4).stream() + .map(i -> i == 0 ? THREE : String.valueOf(i)) + .collect(Collectors.toList()); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer.java new file mode 100644 index 00000000000000..2c16f01bb0b545 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer.java @@ -0,0 +1,174 @@ +// 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.testdata.java8; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.ArrayList; +import java.util.List; + +/** + * Interfaces with default methods are intialized differently from those without default methods. + * When we load such an interface, its static intializer will be executed. + * + *

    However, interfaces without default methods are only initialized when their non-primitive + * fields are accessed. + * + *

    Test data for b/38255926 + */ +public class DefaultInterfaceMethodWithStaticInitializer { + + final List initializationOrder = new ArrayList<>(); + + @CanIgnoreReturnValue + DefaultInterfaceMethodWithStaticInitializer register(Class enclosingInterfaceClass) { + initializationOrder.add(enclosingInterfaceClass.getSimpleName()); + return this; + } + + private static long getTime() { + return 0; + } + + /** The simplest case: direct implementation. */ + public static class TestInterfaceSetOne { + + /** + * A writable field so that other interfaces can set it in their static initializers. + * (b/64290760) + */ + static long writableStaticField; + + static final DefaultInterfaceMethodWithStaticInitializer RECORDER = + new DefaultInterfaceMethodWithStaticInitializer(); + + /** With a default method, this interface should run clinit. */ + interface I1 { + long NOW = TestInterfaceSetOne.writableStaticField = getTime(); + DefaultInterfaceMethodWithStaticInitializer C = RECORDER.register(I1.class); + + default int defaultM1() { + return 1; + } + } + + /** With a default method, this interface should run clinit. */ + interface I2 { + long NOW = TestInterfaceSetOne.writableStaticField = getTime(); + DefaultInterfaceMethodWithStaticInitializer D = RECORDER.register(I2.class); + + default int defaultM2() { + return 10; + } + } + + /** Class to trigger the clinit. */ + public static class C implements I1, I2 { + public int sum() { + return defaultM1() + defaultM2(); + } + } + + public static ImmutableList getExpectedInitializationOrder() { + return ImmutableList.of(I1.class.getSimpleName(), I2.class.getSimpleName()); + } + + public static ImmutableList getRealInitializationOrder() { + return ImmutableList.copyOf(RECORDER.initializationOrder); + } + } + + /** Test for initializer execution order. */ + public static class TestInterfaceSetTwo { + + static final DefaultInterfaceMethodWithStaticInitializer RECORDER = + new DefaultInterfaceMethodWithStaticInitializer(); + + interface I1 { + DefaultInterfaceMethodWithStaticInitializer C = RECORDER.register(I1.class); + + default int defaultM1() { + return 1; + } + } + + interface I2 extends I1 { + DefaultInterfaceMethodWithStaticInitializer D = RECORDER.register(I2.class); + + default int defaultM2() { + return 2; + } + } + + /** + * Loading this class will trigger the execution of the static initializers of I2 and I1. + * However, I1 will be loaded first, as I2 extends I1. + */ + public static class C implements I2, I1 { + protected static final Integer INT_VALUE = Integer.valueOf(1); // To create a + + public int sum() { + return defaultM1() + defaultM2(); + } + } + + public static ImmutableList getExpectedInitializationOrder() { + return ImmutableList.of(I1.class.getSimpleName(), I2.class.getSimpleName()); + } + + public static ImmutableList getRealInitializationOrder() { + return ImmutableList.copyOf(RECORDER.initializationOrder); + } + } + + /** Test: I2's should not be executed. */ + public static class TestInterfaceSetThree { + static final DefaultInterfaceMethodWithStaticInitializer RECORDER = + new DefaultInterfaceMethodWithStaticInitializer(); + + interface I1 { + DefaultInterfaceMethodWithStaticInitializer C = RECORDER.register(I1.class); + + default int defaultM1() { + return 6; + } + } + + interface I2 extends I1 { + default int defaultM2() { + return 5; + } + } + + /** + * Loading this class will trigger the execution of the static initializers of I1. I2's will not + * execute. + */ + public static class C implements I2, I1 { + protected static final Integer INT_VALUE = Integer.valueOf(1); // To create a + + public int sum() { + return defaultM1() + defaultM2(); + } + } + + public static ImmutableList getExpectedInitializationOrder() { + return ImmutableList.of(I1.class.getSimpleName()); + } + + public static ImmutableList getRealInitializationOrder() { + return ImmutableList.copyOf(RECORDER.initializationOrder); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithBridges.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithBridges.java new file mode 100644 index 00000000000000..bd1e0d5161c8de --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithBridges.java @@ -0,0 +1,66 @@ +// 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.testdata.java8; + +/** + * The base interface, which is generic, and has two default methods. These two default methods will + * introduce bridge methods in the child-interfaces + */ +interface GenericInterfaceWithDefaultMethod { + default T copy(T t) { + return t; + } + + default Number getNumber() { + return 1; + } +} + +/** This interface generate two additional bridge methods */ +interface InterfaceWithDefaultAndBridgeMethods extends GenericInterfaceWithDefaultMethod { + @Override + default Integer copy(Integer t) { + return GenericInterfaceWithDefaultMethod.super.copy(t); + } + + @Override + default Double getNumber() { + return 2.3d; + } +} + +/** A class implementing the interface. */ +class ClassWithDefaultAndBridgeMethods implements InterfaceWithDefaultAndBridgeMethods {} + +/** The client class that uses the interfaces and the class that implements the interfaces. */ +public class DefaultInterfaceWithBridges { + private final ClassWithDefaultAndBridgeMethods c = new ClassWithDefaultAndBridgeMethods(); + + public Integer copy(Integer i) { + return c.copy(i); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public Number copy(Number n) { + return ((GenericInterfaceWithDefaultMethod) c).copy(n); + } + + public Number getNumber() { + return ((GenericInterfaceWithDefaultMethod) c).getNumber(); + } + + public Double getDouble() { + return c.getNumber(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithLambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithLambda.java new file mode 100644 index 00000000000000..2a420591e698c4 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithLambda.java @@ -0,0 +1,34 @@ +// 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.testdata.java8; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.stream.Collectors; + +public interface DefaultInterfaceWithLambda { + String ZERO = String.valueOf(0); + + public default List defaultWithLambda() { + return ImmutableList.of(0, 1).stream() + .map(i -> i == 0 ? ZERO : String.valueOf(i)) + .collect(Collectors.toList()); + } + + public default List defaultCallsInterfaceMethod() { + return digits(); + } + + public List digits(); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8Target.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8Target.java new file mode 100644 index 00000000000000..0bd69c02371727 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8Target.java @@ -0,0 +1,19 @@ +// 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.testdata.java8; + +import com.google.devtools.build.android.desugar.testdata.separate8.SeparateInterfaceWithDefaultMethod; + +/** Test class that inherits default method defined in separate target for testing b/65645388. */ +public class DefaultMethodFromSeparateJava8Target implements SeparateInterfaceWithDefaultMethod {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8TargetOverridden.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8TargetOverridden.java new file mode 100644 index 00000000000000..1613f8e64a5aa0 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8TargetOverridden.java @@ -0,0 +1,25 @@ +// 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.testdata.java8; + +import com.google.devtools.build.android.desugar.testdata.separate8.SeparateInterfaceThatInheritsDefaultMethod; + +/** Test class that overrides default method defined in separate target for testing b/65645388. */ +public class DefaultMethodFromSeparateJava8TargetOverridden + implements SeparateInterfaceThatInheritsDefaultMethod { + @Override + public String dflt() { + return "override"; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodTransitivelyFromSeparateJava8Target.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodTransitivelyFromSeparateJava8Target.java new file mode 100644 index 00000000000000..693eaf741fedb7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodTransitivelyFromSeparateJava8Target.java @@ -0,0 +1,23 @@ +// 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.testdata.java8; + +import com.google.devtools.build.android.desugar.testdata.separate8.SeparateInterfaceThatInheritsDefaultMethod; + +/** + * Test class that transitively inherits default method defined in separate target for testing + * b/65645388. + */ +public class DefaultMethodTransitivelyFromSeparateJava8Target + implements SeparateInterfaceThatInheritsDefaultMethod {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod.java new file mode 100644 index 00000000000000..5ff35ec2d46191 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod.java @@ -0,0 +1,78 @@ +// 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.testdata.java8; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** Desugaring test input interface that includes a default method and a static method. */ +public interface FunctionWithDefaultMethod extends Function { + + @Override + T apply(T input); + + static Function toLong() { + return input -> input.longValue(); + } + + default T twice(T input) { + return apply(apply(input)); + } + + /** Don't call this method from tests, it won't work since Desugar moves it! */ + static FunctionWithDefaultMethod inc(int add) { + return input -> input + add; + } + + /** + * Implementation of {@link FunctionWithDefaultMethod} that overrides the default method. Also + * declares static methods the test uses to exercise the code in this file. + */ + public static class DoubleInts implements FunctionWithDefaultMethod { + @Override + public Integer apply(Integer input) { + return 2 * input; + } + + @Override + public Integer twice(Integer input) { + return 5 * input; // deliberately wrong :) + } + + public static List add(List ints, int add) { + return ints.stream().map(inc(add)).map(toLong()).collect(Collectors.toList()); + } + + public static FunctionWithDefaultMethod doubleLambda() { + return input -> 2 * input; + } + + public static FunctionWithDefaultMethod incTwice(int add) { + return inc(add)::twice; + } + + public static FunctionWithDefaultMethod times5() { + return new DoubleInts2()::twice; + } + + public static Function> incFactory() { + return FunctionWithDefaultMethod::inc; + } + } + + /** Empty subclass that explicitly implements the interface the superclass already implements. */ + public static class DoubleInts2 extends DoubleInts + implements FunctionWithDefaultMethod {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/FunctionalInterfaceWithInitializerAndDefaultMethods.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/FunctionalInterfaceWithInitializerAndDefaultMethods.java new file mode 100644 index 00000000000000..cde6c7b6794142 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/FunctionalInterfaceWithInitializerAndDefaultMethods.java @@ -0,0 +1,60 @@ +// 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.testdata.java8; + +/** + * An interface that has a default method, and a non-empty static initializer. The initializer is + * NOT expected to run during desugaring. + */ +public interface FunctionalInterfaceWithInitializerAndDefaultMethods { + + ClassWithInitializer CONSTANT = new ClassWithInitializer(); + boolean BOOLEAN = getFalse(); + char CHAR = "hello".charAt(0); + byte BYTE = Byte.parseByte("0"); + short SHORT = Short.parseShort("0"); + int INT = Integer.parseInt("0"); + float FLOAT = Float.parseFloat("0"); + long LONG = Long.parseLong("0"); + double DOUBLE = Double.parseDouble("0"); + + int convert(); + + /** + * The default method ensures that the static initializer of this interface will be executed when + * the interface is loaded. + */ + default void defaultMethod() {} + + static boolean getFalse() { + return false; + } + + /** + * A class with a static initializer that has side effects (In this class, the printing to stdout) + */ + class ClassWithInitializer { + static { + System.out.println("THIS STRING IS NOT EXPECTED TO APPEAR IN THE OUTPUT OF DESUGAR!!!"); + } + + /** + * A lambda to trigger Desugar to load the interface {@link + * FunctionalInterfaceWithInitializerAndDefaultMethods} + */ + public FunctionalInterfaceWithInitializerAndDefaultMethods length(String s) { + return s::length; + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda.java new file mode 100644 index 00000000000000..58396794bf8283 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda.java @@ -0,0 +1,97 @@ +// 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.testdata.java8; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** An interface with default methods, lambdas, and generics */ +public interface GenericDefaultInterfaceWithLambda { + + T getBaseValue(); + + T increment(T value); + + String toString(T value); + + public default ArrayList toList(int bound) { + ArrayList result = new ArrayList<>(); + if (bound == 0) { + return result; + } + result.add(getBaseValue()); + for (int i = 1; i < bound; ++i) { + result.add(increment(result.get(i - 1))); + } + return result; + } + + public default List convertToStringList(List list) { + return list.stream().map(this::toString).collect(Collectors.toList()); + } + + public default Function> toListSupplier() { + return this::toList; + } + + /** The type parameter is concretized to {@link Number} */ + interface LevelOne extends GenericDefaultInterfaceWithLambda {} + + /** The type parameter is instantiated to {@link Integer} */ + interface LevelTwo extends LevelOne { + + @Override + default Integer getBaseValue() { + return 0; + } + } + + /** An abstract class with no implementing methods. */ + abstract static class ClassOne implements LevelTwo {} + + /** A class for {@link Integer} */ + class ClassTwo extends ClassOne { + + @Override + public Integer increment(Integer value) { + return value + 1; + } + + @Override + public String toString(Integer value) { + return value.toString(); + } + } + + /** A class fo {@link Long} */ + class ClassThree implements LevelOne { + + @Override + public Long getBaseValue() { + return Long.valueOf(0); + } + + @Override + public Long increment(Long value) { + return value + 1; + } + + @Override + public String toString(Long value) { + return value.toString(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethod.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethod.java new file mode 100644 index 00000000000000..22839a6e0e701e --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethod.java @@ -0,0 +1,64 @@ +// 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.testdata.java8; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Desugar test input interface that declares lambdas and method references in default and static + * interface methods. + */ +public interface InterfaceMethod { + public default List defaultMethodReference(List names) { + return names.stream().filter(this::startsWithS).collect(Collectors.toList()); + } + + public default String defaultInvokingBootclasspathMethods(String expectedValue) { + return Stream.of(expectedValue).findFirst().orElse("unexpected"); + } + + public default List staticMethodReference(List names) { + return names.stream().filter(InterfaceMethod::startsWithA).collect(Collectors.toList()); + } + + public default List lambdaCallsDefaultMethod(List names) { + return names.stream().filter(s -> startsWithS(s)).collect(Collectors.toList()); + } + + public static boolean startsWithA(String input) { + return input.startsWith("A"); + } + + public default boolean startsWithS(String input) { + return input.startsWith("S"); + } + + // Same descriptor as when method above is turned into static method + public default boolean startsWithS(InterfaceMethod receiver, String input) { + return startsWithS(input); + } + + /** + * Empty class implementing {@link InterfaceMethod} so the test can instantiate and call default + * methods. + */ + public static class Concrete implements InterfaceMethod { + // We will rewrite this class to subclass InterfaceMethod's companion class, and call its super + // constructor. This field is here to make sure we don't also rewrite its constructor call. + @SuppressWarnings("unused") + private final Object o = new Object(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodParameters.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodParameters.java new file mode 100644 index 00000000000000..669724da7e23d7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodParameters.java @@ -0,0 +1,18 @@ +package com.google.devtools.build.android.desugar.testdata.java8; + +import com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethodWithParam.Foo; +import com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethodWithParam.TyFoo; + +/** A testing interface subject to the inspection its compiled bytecode. */ +public interface InterfaceMethodParameters { + + @Foo + static @TyFoo int simpleComputeStatic(int x, @Foo @TyFoo int y) { + return x + y; + } + + @Foo + default @TyFoo int simpleComputeDefault(int x, @Foo @TyFoo int y) { + return x + y; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam.java new file mode 100644 index 00000000000000..c37a205ba5db5e --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam.java @@ -0,0 +1,106 @@ +package com.google.devtools.build.android.desugar.testdata.java8; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; + +/** Interface for testing the parameters of static and default methods. */ +public interface InterfaceMethodWithParam { + + /** For testing the annotations on the parameters of interface static and default methods */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER, ElementType.METHOD}) + @interface Foo { + String value() default "default-attr"; + } + + /** For testing the annotations on the parameters of interface static and default methods */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE_USE}) + @interface TyFoo {} + + static long simpleComputeStatic(int x) { + return (long) x; + } + + default long simpleComputeDefault(int x) { + return (long) x; + } + + /** + * @param v4897b02fddeda3bb31bc15b3cad0f6febc61508 a cryptic parameter name for checking whether + * the name is available at run-time. The name is from $ echo "desugar-static" | sha1sum + * @return The reflection representation of the method itself. + */ + @Foo("custom-attr-value-1") + static @TyFoo Method inspectCompanionMethodOfStaticMethod( + @Foo @TyFoo String v4897b02fddeda3bb31bc15b3cad0f6febc61508) throws Exception { + return TestHelper.getEnclosingRuntimeMethod( + new Throwable(v4897b02fddeda3bb31bc15b3cad0f6febc61508)); + } + + /** + * @param v12525d61e4b10b3e27bc280dd61e56728e3e8c27 A cryptic parameter name for checking whether + * the name is available at run-time. The name is from $ echo "desugar-default" | sha1sum + * @return The reflection representation of the method itself. + */ + @Foo("custom-attr-value-2") + default @TyFoo Method inspectCompanionMethodOfDefaultMethod( + @Foo @TyFoo String v12525d61e4b10b3e27bc280dd61e56728e3e8c27) throws Exception { + return TestHelper.getEnclosingRuntimeMethod( + new Throwable(v12525d61e4b10b3e27bc280dd61e56728e3e8c27)); + } + + /** A concrete class that implements an interface with static methods and default methods. */ + final class Concrete implements InterfaceMethodWithParam {} + + /** Test cases for the invocations of interface static methods and default methods. */ + final class MethodInvocations { + + public static long simpleComputeStatic(int x) { + return InterfaceMethodWithParam.simpleComputeStatic(x); + } + + public static long simpleComputeDefault(int x) { + Concrete concrete = new Concrete(); + return concrete.simpleComputeDefault(x); + } + + public static Method inspectDesugaredDefaultMethod() throws Exception { + return InterfaceMethodWithParam.class.getDeclaredMethod( + "inspectCompanionMethodOfDefaultMethod", String.class); + } + + public static Method inspectCompanionOfStaticMethod() throws Exception { + return InterfaceMethodWithParam.inspectCompanionMethodOfStaticMethod("random-input"); + } + + public static Method inspectCompanionMethodOfDefaultMethod() throws Exception { + Concrete concrete = new Concrete(); + return concrete.inspectCompanionMethodOfDefaultMethod("random-input"); + } + } + + /** A helper class that interface methods with parameters. */ + final class TestHelper { + + /** Returns the runtime-invoked method that encloses {@code enclosedThrowable}. */ + static Method getEnclosingRuntimeMethod(Throwable enclosedThrowable) + throws ClassNotFoundException, NoSuchMethodException { + StackTraceElement stackTraceElement = enclosedThrowable.getStackTrace()[0]; + String methodName = stackTraceElement.getMethodName(); + String className = stackTraceElement.getClassName(); + for (Method method : Class.forName(className).getDeclaredMethods()) { + if (methodName.equals(method.getName())) { + return method; + } + } + throw new NoSuchMethodException(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultMethod.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultMethod.java new file mode 100644 index 00000000000000..ddc87b686c0122 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultMethod.java @@ -0,0 +1,41 @@ +// 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.testdata.java8; + +/** Interface for testing default methods overridden by extending interfaces. */ +public interface InterfaceWithDefaultMethod { + default int version() { + return 1; + } + + /** Interface that overrides {@link #version}. */ + public interface Redefine extends InterfaceWithDefaultMethod { + @Override + default int version() { + return 2; + } + } + + /** Class that implements both interfaces, supertype before subtype. */ + public static class Version2 implements InterfaceWithDefaultMethod, Redefine {} + + /** Base class that just implements {@link Redefine}. */ + static class Version2Base implements Redefine {} + + /** + * Subclass that implements an interface explicitly that the superclass also implements, but the + * superclass implements a more specific interface that overrides a defautl method. + */ + public static class AlsoVersion2 extends Version2Base implements InterfaceWithDefaultMethod {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDuplicateMethods.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDuplicateMethods.java new file mode 100644 index 00000000000000..56217b8c086c44 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDuplicateMethods.java @@ -0,0 +1,45 @@ +// 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.testdata.java8; + +/** + * Test for b/38308515. This interface has one instance method {@code m()} and one static method + * {@code m(InterfaceWithDuplicateMethods)}, which may cause Desugar to dump a companion class with + * duplicate method signatures. + */ +public interface InterfaceWithDuplicateMethods { + + /** + * In the companion class, this default method will be transformed to {@code int + * getZero(InterfaceWithDuplicateMethods)}, which has the same signature as the static interface + * method below. + */ + @SuppressWarnings("AmbiguousMethodReference") + default int getZero() { + return 0; + } + + /** Should not be called. Should only be called by the class {@link ClassWithDuplicateMethods} */ + @SuppressWarnings("AmbiguousMethodReference") + static int getZero(InterfaceWithDuplicateMethods i) { + return 1; + } + + /** This class implements the interface, and calls the static interface method. */ + class ClassWithDuplicateMethods implements InterfaceWithDuplicateMethods { + public int getZeroFromStaticInterfaceMethod() { + return InterfaceWithDuplicateMethods.getZero(this); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithInheritedMethods.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithInheritedMethods.java new file mode 100644 index 00000000000000..1336cfa4d3ce45 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithInheritedMethods.java @@ -0,0 +1,44 @@ +// Copyright 2018 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.testdata.java8; + +/** Regression test data for b/73355452 that also includes calling static methods. */ +public interface InterfaceWithInheritedMethods { + default String name() { + return "Base"; + } + + static String staticSuffix() { + return "!"; + } + + static interface Passthrough extends InterfaceWithInheritedMethods { + // inherits name(). Note that desugar doesn't produce a companion class for this interface + // since it doesn't define any default or static interface methods itself. + } + + static class Impl implements Passthrough { + @Override + public String name() { + // Even though Passthrough itself doesn't define name(), bytecode refers to Passthrough.name. + return Passthrough.super.name(); + } + + public String suffix() { + // Note that Passthrough.defaultSuffix doesn't compile and bytecode refers to + // InterfaceWithInheritedMethods.staticSuffix, so this shouldn't cause issues like b/73355452 + return staticSuffix(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges.java new file mode 100644 index 00000000000000..ae9ef4f765b5f0 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges.java @@ -0,0 +1,56 @@ +// 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.testdata.java8; + +/** + * The test classes for desugaring default methods. Desugar is not expected to generate companion + * classes for interfaces without default methods. The bridge methods are automatically generated by + * javac and put in the implementing classes. + * + *

    NOTE: There should be NO companion class generated for this class. + */ +public interface Java7InterfaceWithBridges { + T add(T value); + + /** Concretize T to {@link Number)} */ + interface LevelOne extends Java7InterfaceWithBridges { + @Override + T add(T value); + } + + /** Concretize to {@link Integer} */ + interface LevelTwo extends LevelOne { + @Override + Integer add(Integer value); + } + + /** Empty abstract class. This class should have no bridge methods */ + abstract static class AbstractClassOne implements LevelTwo {} + + /** Implementing class. */ + static class ClassAddOne extends AbstractClassOne { + @Override + public Integer add(Integer value) { + return value + 1; + } + } + + /** Implementing class. */ + static class ClassAddTwo extends AbstractClassOne implements LevelTwo { + @Override + public Integer add(Integer value) { + return value + 2; + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/Named.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/Named.java new file mode 100644 index 00000000000000..b50c3387daa1dd --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/Named.java @@ -0,0 +1,72 @@ +// 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.testdata.java8; + +/** Desugar test interface to test precedence of inherited methods over default methods. */ +public interface Named { + default String name() { + return getClass().getSimpleName(); + } + + /** Base class defining {@link #name} without implementing {@link Named}. */ + static class ExplicitNameBase { + private final String name; + + public ExplicitNameBase(String name) { + this.name = name; + } + + public String name() { + return name; + } + } + + /** Class whose base class implements {@link #name}. */ + public static class ExplicitName extends ExplicitNameBase implements Named { + public ExplicitName(String name) { + super(name); + } + } + + /** Class that explicitly defers to the default method in {@link Named}. */ + public static class DefaultName extends ExplicitNameBase implements Named { + public DefaultName() { + super(null); + } + + @Override + public String name() { + return Named.super.name() + "-once"; + } + } + + /** Subclass of {@link DefaultName} that uses {@code super} as well. */ + public static class DefaultNameSubclass extends DefaultName { + @Override + public String name() { + return super.name() + "-twice"; + } + } + + /** Base class that declares {@link #name} abstract. */ + abstract static class AbstractNameBase { + public abstract String name(); + } + + /** + * Class that inherits {@link #name} abstract so subclasses must implement it despite default + * method in implemented interface. + */ + public abstract static class AbstractName extends AbstractNameBase implements Named {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/TwoInheritedDefaultMethods.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/TwoInheritedDefaultMethods.java new file mode 100644 index 00000000000000..e9456ead995e4e --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/TwoInheritedDefaultMethods.java @@ -0,0 +1,36 @@ +// 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.testdata.java8; + +/** Desugar test class that explicitly calls default methods from two implemented interfaces. */ +public class TwoInheritedDefaultMethods implements Name1, Name2 { + @Override + public String name() { + return Name1.super.name() + ":" + Name2.super.name(); + } +} + +/** Test interface for {@link TwoInheritedDefaultMethods}. */ +interface Name1 { + default String name() { + return "One"; + } +} + +/** Test interface for {@link TwoInheritedDefaultMethods}. */ +interface Name2 { + default String name() { + return "Two"; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/VisibilityTestClass.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/VisibilityTestClass.java new file mode 100644 index 00000000000000..3fefcc11d0907c --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/VisibilityTestClass.java @@ -0,0 +1,22 @@ +// 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.testdata.java8; + +import com.google.devtools.build.android.desugar.testdata.java8.subpackage.PublicInterface; + +/** + * Class that transitively implements a package-private interface in another package. Default method + * desugaring will need to make the default method defined in that interface publicly accessible. + */ +public class VisibilityTestClass implements PublicInterface {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/subpackage/PackagePrivateInterface.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/subpackage/PackagePrivateInterface.java new file mode 100644 index 00000000000000..9153c122640626 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/subpackage/PackagePrivateInterface.java @@ -0,0 +1,33 @@ +// 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.testdata.java8.subpackage; + +/** Package-private interface with default method. */ +interface PackagePrivateInterface { + + /** + * This field makes this interface need to be initialized. With the default methods, when this + * interface is loaded, its initializer should also be run. + * + *

    However, this test interface is different, as it is package-private. We need to to make sure + * the desugared code does not trigger IllegalAccessError. + * + *

    See b/38255926. + */ + Integer VERSION = Integer.valueOf(0); + + default int m() { + return 42; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/subpackage/PublicInterface.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/subpackage/PublicInterface.java new file mode 100644 index 00000000000000..c3d51db446884b --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/java8/subpackage/PublicInterface.java @@ -0,0 +1,22 @@ +// 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.testdata.java8.subpackage; + +/** + * Public interface extending a package-private interface so classes in other packages can + * transitively implement a package-private interface. + * + * @see com.google.devtools.build.android.desugar.testdata.java8.VisibilityTestClass + */ +public interface PublicInterface extends PackagePrivateInterface {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/separate/SeparateBaseClass.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/separate/SeparateBaseClass.java new file mode 100644 index 00000000000000..a1eceddbd79c1c --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/separate/SeparateBaseClass.java @@ -0,0 +1,30 @@ +// 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.testdata.separate; + +import java.util.List; + +/** Test base class for testing method references to protected methods in another compilation. */ +public class SeparateBaseClass { + + private final List list; + + protected SeparateBaseClass(List list) { + this.list = list; + } + + protected boolean contains(T elem) { + return list.contains(elem); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/separate/SeparateInterface.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/separate/SeparateInterface.java new file mode 100644 index 00000000000000..ce4b0584b90cee --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/separate/SeparateInterface.java @@ -0,0 +1,21 @@ +// 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.testdata.separate; + +import java.util.function.Predicate; + +public interface SeparateInterface extends Predicate { + @Override + boolean test(T input); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/separate8/SeparateInterfaceThatInheritsDefaultMethod.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/separate8/SeparateInterfaceThatInheritsDefaultMethod.java new file mode 100644 index 00000000000000..400a6f19dd1160 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/separate8/SeparateInterfaceThatInheritsDefaultMethod.java @@ -0,0 +1,18 @@ +// 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.testdata.separate8; + +/** Interface that inherits default method in separate compilation target for testing b/65645388. */ +public interface SeparateInterfaceThatInheritsDefaultMethod + extends SeparateInterfaceWithDefaultMethod {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/separate8/SeparateInterfaceWithDefaultMethod.java b/src/test/java/com/google/devtools/build/android/desugar/testdata/separate8/SeparateInterfaceWithDefaultMethod.java new file mode 100644 index 00000000000000..b3c04c8c2d5b72 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/separate8/SeparateInterfaceWithDefaultMethod.java @@ -0,0 +1,21 @@ +// 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.testdata.separate8; + +/** Interface with default method in separate compilation target for testing b/65645388. */ +public interface SeparateInterfaceWithDefaultMethod { + default String dflt() { + return "dflt"; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata/testresource.txt b/src/test/java/com/google/devtools/build/android/desugar/testdata/testresource.txt new file mode 100644 index 00000000000000..30d74d258442c7 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata/testresource.txt @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_core_library_jar_toc_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_core_library_jar_toc_golden.txt new file mode 100644 index 00000000000000..cb0d40c97e5691 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_core_library_jar_toc_golden.txt @@ -0,0 +1,18 @@ +META-INF/ +META-INF/MANIFEST.MF +com/ +com/google/ +com/google/devtools/ +com/google/devtools/build/ +com/google/devtools/build/android/ +com/google/devtools/build/android/desugar/ +com/google/devtools/build/android/desugar/testdata/ +com/google/devtools/build/android/desugar/testdata/testresource.txt +java/ +java/lang/ +java/lang/AutoboxedTypes$Lambda.class +java/lang/AutoboxedTypes.class +test/ +test/util/ +test/util/TestClassForStackMapFrame.class +java/lang/AutoboxedTypes$$Lambda$0.class \ No newline at end of file diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_for_disabling_twr_with_large_minsdkversion_jar_toc_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_for_disabling_twr_with_large_minsdkversion_jar_toc_golden.txt new file mode 100644 index 00000000000000..0aa80f5368d51d --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_for_disabling_twr_with_large_minsdkversion_jar_toc_golden.txt @@ -0,0 +1,68 @@ +META-INF/ +META-INF/MANIFEST.MF +com/ +com/google/ +com/google/devtools/ +com/google/devtools/build/ +com/google/devtools/build/android/ +com/google/devtools/build/android/desugar/ +com/google/devtools/build/android/desugar/testdata/ +com/google/devtools/build/android/desugar/testdata/testresource.txt +com/google/devtools/build/android/desugar/testdata/CaptureLambda.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$LongCmpFunc.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$InheritanceResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleCloseable$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleCloseable.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$Parser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$SpecializedParser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$Empty.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference.class +com/google/devtools/build/android/desugar/testdata/GuavaLambda.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda.class +com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda.class +com/google/devtools/build/android/desugar/testdata/Lambda$1.class +com/google/devtools/build/android/desugar/testdata/Lambda.class +com/google/devtools/build/android/desugar/testdata/LambdaInOverride.class +com/google/devtools/build/android/desugar/testdata/MethodReference$Transformer.class +com/google/devtools/build/android/desugar/testdata/MethodReference.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.class +com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda.class +com/google/devtools/build/android/desugar/testdata/SpecializedFunction.class +com/google/devtools/build/android/desugar/testdata/CaptureLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/GuavaLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1$1$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$1$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/LambdaInOverride$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$3.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$4.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$5.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$6.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$7.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda$$Lambda$0.class diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_for_try_with_resources_jar_toc_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_for_try_with_resources_jar_toc_golden.txt new file mode 100644 index 00000000000000..9655938e932457 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_for_try_with_resources_jar_toc_golden.txt @@ -0,0 +1,75 @@ +META-INF/ +META-INF/MANIFEST.MF +com/ +com/google/ +com/google/devtools/ +com/google/devtools/build/ +com/google/devtools/build/android/ +com/google/devtools/build/android/desugar/ +com/google/devtools/build/android/desugar/testdata/ +com/google/devtools/build/android/desugar/testdata/testresource.txt +com/google/devtools/build/android/desugar/testdata/CaptureLambda.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$LongCmpFunc.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$InheritanceResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleCloseable$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleCloseable.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$Parser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$SpecializedParser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$Empty.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference.class +com/google/devtools/build/android/desugar/testdata/GuavaLambda.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda.class +com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda.class +com/google/devtools/build/android/desugar/testdata/Lambda$1.class +com/google/devtools/build/android/desugar/testdata/Lambda.class +com/google/devtools/build/android/desugar/testdata/LambdaInOverride.class +com/google/devtools/build/android/desugar/testdata/MethodReference$Transformer.class +com/google/devtools/build/android/desugar/testdata/MethodReference.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.class +com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda.class +com/google/devtools/build/android/desugar/testdata/SpecializedFunction.class +com/google/devtools/build/android/desugar/testdata/CaptureLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/GuavaLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1$1$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$1$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/LambdaInOverride$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$3.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$4.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$5.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$6.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$7.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$AbstractDesugaringStrategy.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$ConcurrentWeakIdentityHashMap.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$ConcurrentWeakIdentityHashMap$WeakKey.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$MimicDesugaringStrategy.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$NullDesugaringStrategy.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$ReuseDesugaringStrategy.class diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_jar_test.sh b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_jar_test.sh new file mode 100755 index 00000000000000..464917075a8c6d --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_jar_test.sh @@ -0,0 +1,47 @@ +#!/bin/bash -e +# +# 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. + +# Test that lists the content of the desugared Jar and compares it to a golden +# file. This makes sure that output is deterministic and the resulting Jar +# doesn't contain any unwanted files, such as lambdas generated as part of +# running the desugaring tool. + +progdir="$(dirname "$0")" + +if [ -d "$TEST_TMPDIR" ]; then + # Running as part of blaze test + tmpdir="$TEST_TMPDIR" +else + # Manual run from command line + tmpdir="/tmp/test-$$" + mkdir "${tmpdir}" +fi + +if [ -d "$TEST_UNDECLARED_OUTPUTS_DIR" ]; then + # Running as part of blaze test: capture test output + output="$TEST_UNDECLARED_OUTPUTS_DIR" +else + # Manual run from command line: just write into temp dir + output="${tmpdir}" +fi + +JAVABASE=$3 +# Dump Jar file contents but drop coverage artifacts in case coverage is enabled +$JAVABASE/bin/jar tf "$1" \ + | grep -v '\.uninstrumented$' \ + | grep -v '\-paths\-for\-coverage\.txt$' >"${output}/actual_toc.txt" +# sorting can be removed when cl/145334839 is released +diff <(sort "$2") <(sort "${output}/actual_toc.txt") diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_jar_toc_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_jar_toc_golden.txt new file mode 100644 index 00000000000000..9655938e932457 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_jar_toc_golden.txt @@ -0,0 +1,75 @@ +META-INF/ +META-INF/MANIFEST.MF +com/ +com/google/ +com/google/devtools/ +com/google/devtools/build/ +com/google/devtools/build/android/ +com/google/devtools/build/android/desugar/ +com/google/devtools/build/android/desugar/testdata/ +com/google/devtools/build/android/desugar/testdata/testresource.txt +com/google/devtools/build/android/desugar/testdata/CaptureLambda.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$LongCmpFunc.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$InheritanceResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleCloseable$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleCloseable.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$Parser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$SpecializedParser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$Empty.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference.class +com/google/devtools/build/android/desugar/testdata/GuavaLambda.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda.class +com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda.class +com/google/devtools/build/android/desugar/testdata/Lambda$1.class +com/google/devtools/build/android/desugar/testdata/Lambda.class +com/google/devtools/build/android/desugar/testdata/LambdaInOverride.class +com/google/devtools/build/android/desugar/testdata/MethodReference$Transformer.class +com/google/devtools/build/android/desugar/testdata/MethodReference.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.class +com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda.class +com/google/devtools/build/android/desugar/testdata/SpecializedFunction.class +com/google/devtools/build/android/desugar/testdata/CaptureLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/GuavaLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1$1$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$1$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/LambdaInOverride$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$3.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$4.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$5.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$6.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$7.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$AbstractDesugaringStrategy.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$ConcurrentWeakIdentityHashMap.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$ConcurrentWeakIdentityHashMap$WeakKey.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$MimicDesugaringStrategy.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$NullDesugaringStrategy.class +com/google/devtools/build/android/desugar/runtime/ThrowableExtension$ReuseDesugaringStrategy.class diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_java8_jar_toc_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_java8_jar_toc_golden.txt new file mode 100644 index 00000000000000..9322b88542f636 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_java8_jar_toc_golden.txt @@ -0,0 +1,161 @@ +META-INF/ +META-INF/MANIFEST.MF +com/ +com/google/ +com/google/devtools/ +com/google/devtools/build/ +com/google/devtools/build/android/ +com/google/devtools/build/android/desugar/ +com/google/devtools/build/android/desugar/testdata/ +com/google/devtools/build/android/desugar/testdata/testresource.txt +com/google/devtools/build/android/desugar/testdata/CaptureLambda.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$LongCmpFunc.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$InheritanceResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleCloseable$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleCloseable.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$Parser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$SpecializedParser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$Empty.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference.class +com/google/devtools/build/android/desugar/testdata/GuavaLambda.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda.class +com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda.class +com/google/devtools/build/android/desugar/testdata/Lambda$1.class +com/google/devtools/build/android/desugar/testdata/Lambda.class +com/google/devtools/build/android/desugar/testdata/LambdaInOverride.class +com/google/devtools/build/android/desugar/testdata/MethodReference$Transformer.class +com/google/devtools/build/android/desugar/testdata/MethodReference.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.class +com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda.class +com/google/devtools/build/android/desugar/testdata/SpecializedFunction.class +com/google/devtools/build/android/desugar/testdata/java8/ +com/google/devtools/build/android/desugar/testdata/java8/AnnotationsOfDefaultMethodsShouldBeKept$AnnotatedInterface.class +com/google/devtools/build/android/desugar/testdata/java8/AnnotationsOfDefaultMethodsShouldBeKept$SomeAnnotation.class +com/google/devtools/build/android/desugar/testdata/java8/AnnotationsOfDefaultMethodsShouldBeKept.class +com/google/devtools/build/android/desugar/testdata/java8/ClassWithDefaultAndBridgeMethods.class +com/google/devtools/build/android/desugar/testdata/java8/ConcreteDefaultInterfaceWithLambda.class +com/google/devtools/build/android/desugar/testdata/java8/ConcreteOverridesDefaultWithLambda.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetOne$C.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetOne$I1.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetOne$I2.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetOne.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetThree$C.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetThree$I1.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetThree$I2.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetThree.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetTwo$C.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetTwo$I1.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetTwo$I2.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetTwo.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceMethodWithStaticInitializer.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithBridges.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithLambda.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8Target.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodFromSeparateJava8TargetOverridden.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultMethodTransitivelyFromSeparateJava8Target.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod$DoubleInts.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod$DoubleInts2.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionalInterfaceWithInitializerAndDefaultMethods$ClassWithInitializer.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionalInterfaceWithInitializerAndDefaultMethods.class +com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda$ClassOne.class +com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda$ClassThree.class +com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda$ClassTwo.class +com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda$LevelOne.class +com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda$LevelTwo.class +com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda.class +com/google/devtools/build/android/desugar/testdata/java8/GenericInterfaceWithDefaultMethod.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodParameters.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam$Concrete.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam$Foo.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam$TyFoo.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam$MethodInvocations.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam$TestHelper.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethodWithParam.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethod$Concrete.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethod.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultAndBridgeMethods.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultMethod$AlsoVersion2.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultMethod$Redefine.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultMethod$Version2.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultMethod$Version2Base.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDefaultMethod.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDuplicateMethods$ClassWithDuplicateMethods.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithDuplicateMethods.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithInheritedMethods$Impl.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithInheritedMethods$Passthrough.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceWithInheritedMethods.class +com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges$AbstractClassOne.class +com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges$ClassAddOne.class +com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges$ClassAddTwo.class +com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges$LevelOne.class +com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges$LevelTwo.class +com/google/devtools/build/android/desugar/testdata/java8/Java7InterfaceWithBridges.class +com/google/devtools/build/android/desugar/testdata/java8/Name1.class +com/google/devtools/build/android/desugar/testdata/java8/Name2.class +com/google/devtools/build/android/desugar/testdata/java8/Named$AbstractName.class +com/google/devtools/build/android/desugar/testdata/java8/Named$AbstractNameBase.class +com/google/devtools/build/android/desugar/testdata/java8/Named$DefaultName.class +com/google/devtools/build/android/desugar/testdata/java8/Named$DefaultNameSubclass.class +com/google/devtools/build/android/desugar/testdata/java8/Named$ExplicitName.class +com/google/devtools/build/android/desugar/testdata/java8/Named$ExplicitNameBase.class +com/google/devtools/build/android/desugar/testdata/java8/Named.class +com/google/devtools/build/android/desugar/testdata/java8/TwoInheritedDefaultMethods.class +com/google/devtools/build/android/desugar/testdata/java8/VisibilityTestClass.class +com/google/devtools/build/android/desugar/testdata/java8/subpackage/ +com/google/devtools/build/android/desugar/testdata/java8/subpackage/PackagePrivateInterface.class +com/google/devtools/build/android/desugar/testdata/java8/subpackage/PublicInterface.class +com/google/devtools/build/android/desugar/testdata/CaptureLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/GuavaLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1$1$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$1$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/Lambda$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/LambdaInOverride$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$3.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$4.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$5.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$6.class +com/google/devtools/build/android/desugar/testdata/MethodReference$$Lambda$7.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/java8/ConcreteDefaultInterfaceWithLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/java8/ConcreteOverridesDefaultWithLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/java8/ConcreteOverridesDefaultWithLambda$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/java8/DefaultInterfaceWithLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod$DoubleInts$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod$DoubleInts$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod$DoubleInts$$Lambda$2.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod$DoubleInts$$Lambda$3.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionWithDefaultMethod$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/java8/FunctionalInterfaceWithInitializerAndDefaultMethods$ClassWithInitializer$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/java8/GenericDefaultInterfaceWithLambda$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethod$$Lambda$0.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethod$$Lambda$1.class +com/google/devtools/build/android/desugar/testdata/java8/InterfaceMethod$$Lambda$2.class diff --git a/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_without_lambda_desugared_jar_toc_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_without_lambda_desugared_jar_toc_golden.txt new file mode 100644 index 00000000000000..4c21ef88c4c2a2 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testdata_desugared_without_lambda_desugared_jar_toc_golden.txt @@ -0,0 +1,38 @@ +META-INF/ +META-INF/MANIFEST.MF +com/ +com/google/ +com/google/devtools/ +com/google/devtools/build/ +com/google/devtools/build/android/ +com/google/devtools/build/android/desugar/ +com/google/devtools/build/android/desugar/testdata/ +com/google/devtools/build/android/desugar/testdata/CaptureLambda.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare$LongCmpFunc.class +com/google/devtools/build/android/desugar/testdata/ClassCallingLongCompare.class +com/google/devtools/build/android/desugar/testdata/ClassCallingRequireNonNull.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$InheritanceResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleCloseable.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources$SimpleResource.class +com/google/devtools/build/android/desugar/testdata/ClassUsingTryWithResources.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$Parser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction$SpecializedParser.class +com/google/devtools/build/android/desugar/testdata/ConcreteFunction.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$1.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference$Empty.class +com/google/devtools/build/android/desugar/testdata/ConstructorReference.class +com/google/devtools/build/android/desugar/testdata/GuavaLambda.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda$1.class +com/google/devtools/build/android/desugar/testdata/InnerClassLambda.class +com/google/devtools/build/android/desugar/testdata/InterfaceWithLambda.class +com/google/devtools/build/android/desugar/testdata/Lambda$1.class +com/google/devtools/build/android/desugar/testdata/Lambda.class +com/google/devtools/build/android/desugar/testdata/LambdaInOverride.class +com/google/devtools/build/android/desugar/testdata/MethodReference$Transformer.class +com/google/devtools/build/android/desugar/testdata/MethodReference.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceInSubclass.class +com/google/devtools/build/android/desugar/testdata/MethodReferenceSuperclass.class +com/google/devtools/build/android/desugar/testdata/OuterReferenceLambda.class +com/google/devtools/build/android/desugar/testdata/SpecializedFunction.class +com/google/devtools/build/android/desugar/testdata/testresource.txt diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyTest.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyTest.java new file mode 100644 index 00000000000000..acfba6ddbe8893 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/TypeHierarchyTest.java @@ -0,0 +1,383 @@ +/* + * Copyright 2020 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.typehierarchy; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Functional tests for {@link TypeHierarchy}. + * + *

      + *
    1. + *

      Mixed classes and interfaces. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      CZ
      CAIA
      CBIB
      CCIC
      CPCIZ
      + *

        + *
      • C: Class + *
      • I: Interface + *
      • P: package + *
      • The arrow indicates class inheritance. + *
      + *
    2. + *

      Overridable methods with package-private visibility. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      CXCY
      BXBY
      AXAY
      + *

        + *
      • X: In package X (pkgx) + *
      • Y: In package Y (pkgy) + *
      + */ +@RunWith(JUnit4.class) +public class TypeHierarchyTest { + + private static final Path INPUT_JAR_PATH = Paths.get(System.getProperty("input_lib")); + private static final String TEST_LIB_ROOT = + "com/google/devtools/build/android/desugar/typehierarchy/testlib/"; + private TypeHierarchy typeHierarchy; + private static final HierarchicalTypeKey OBJECT_CLASS = + HierarchicalTypeKey.create(ClassName.create("java/lang/Object")); + private static final HierarchicalTypeKey CLASS_ALPHA = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkga/ClassAlpha")); + private static final HierarchicalTypeKey INTERFACE_ALPHA = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkga/InterfaceAlpha")); + private static final HierarchicalTypeKey CLASS_BRAVO = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgb/ClassBravo")); + private static final HierarchicalTypeKey INTERFACE_BRAVO = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgb/InterfaceBravo")); + private static final HierarchicalTypeKey CLASS_CHARLIE = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgc/ClassCharlie")); + private static final HierarchicalTypeKey CLASS_PACKAGE_CHARLIE = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgc/ClassPackageCharlie")); + private static final HierarchicalTypeKey INTERFACE_CHARLIE = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgc/InterfaceCharlie")); + private static final HierarchicalTypeKey CLASS_ZULU = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "ClassZulu")); + private static final HierarchicalTypeKey INTERFACE_ZULU = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "InterfaceZulu")); + + private static final HierarchicalTypeKey PIE_AX = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgx/PieAX")); + private static final HierarchicalTypeKey PIE_BX = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgx/PieBX")); + private static final HierarchicalTypeKey PIE_CX = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgx/PieCX")); + private static final HierarchicalTypeKey PIE_AY = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgy/PieAY")); + private static final HierarchicalTypeKey PIE_BY = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgy/PieBY")); + private static final HierarchicalTypeKey PIE_CY = + HierarchicalTypeKey.create(ClassName.create(TEST_LIB_ROOT + "pkgy/PieCY")); + + @Before + public void setUp() { + typeHierarchy = TypeHierarchyScavenger.analyze(ImmutableList.of(INPUT_JAR_PATH), true); + } + + @Test + public void classAlpha() { + HierarchicalTypeQuery classAlpha = CLASS_ALPHA.inTypeHierarchy(typeHierarchy); + + assertThat(classAlpha.findDirectSuperClass()).isEqualTo(CLASS_BRAVO); + assertThat(classAlpha.findDirectSuperInterfaces()) + .containsExactly(INTERFACE_ALPHA, INTERFACE_BRAVO); + assertThat(classAlpha.findDirectSuperTypes()) + .containsExactly(CLASS_BRAVO, INTERFACE_ALPHA, INTERFACE_BRAVO); + + assertThat(classAlpha.findTransitiveSuperClasses()) + .containsExactly(CLASS_BRAVO, CLASS_CHARLIE, CLASS_PACKAGE_CHARLIE, OBJECT_CLASS); + assertThat(classAlpha.findTransitiveSuperInterfaces()) + .containsExactly(INTERFACE_ALPHA, INTERFACE_BRAVO, INTERFACE_CHARLIE, INTERFACE_ZULU); + assertThat(classAlpha.findTransitiveSuperTypes()) + .containsExactly( + CLASS_BRAVO, + CLASS_CHARLIE, + CLASS_PACKAGE_CHARLIE, + OBJECT_CLASS, + INTERFACE_ALPHA, + INTERFACE_BRAVO, + INTERFACE_CHARLIE, + INTERFACE_ZULU); + } + + @Test + public void interfaceAlpha() { + HierarchicalTypeQuery interfaceAlpha = INTERFACE_ALPHA.inTypeHierarchy(typeHierarchy); + + assertThat(interfaceAlpha.findDirectSuperClass()).isEqualTo(OBJECT_CLASS); + assertThat(interfaceAlpha.findDirectSuperInterfaces()) + .containsExactly(INTERFACE_BRAVO, INTERFACE_CHARLIE); + assertThat(interfaceAlpha.findDirectSuperTypes()) + .containsExactly(OBJECT_CLASS, INTERFACE_BRAVO, INTERFACE_CHARLIE); + + assertThat(interfaceAlpha.findTransitiveSuperClasses()).containsExactly(OBJECT_CLASS); + assertThat(interfaceAlpha.findTransitiveSuperInterfaces()) + .containsExactly(INTERFACE_BRAVO, INTERFACE_CHARLIE, INTERFACE_ZULU); + assertThat(interfaceAlpha.findTransitiveSuperTypes()) + .containsExactly(OBJECT_CLASS, INTERFACE_BRAVO, INTERFACE_CHARLIE, INTERFACE_ZULU); + } + + @Test + public void classBravo() { + HierarchicalTypeQuery classBravo = CLASS_BRAVO.inTypeHierarchy(typeHierarchy); + + assertThat(classBravo.findDirectSuperClass()).isEqualTo(CLASS_CHARLIE); + assertThat(classBravo.findDirectSuperInterfaces()) + .containsExactly(INTERFACE_BRAVO, INTERFACE_CHARLIE); + assertThat(classBravo.findDirectSuperTypes()) + .containsExactly(CLASS_CHARLIE, INTERFACE_BRAVO, INTERFACE_CHARLIE); + + assertThat(classBravo.findTransitiveSuperClasses()) + .containsExactly(CLASS_CHARLIE, CLASS_PACKAGE_CHARLIE, OBJECT_CLASS); + assertThat(classBravo.findTransitiveSuperInterfaces()) + .containsExactly(INTERFACE_BRAVO, INTERFACE_CHARLIE, INTERFACE_ZULU); + assertThat(classBravo.findTransitiveSuperTypes()) + .containsExactly( + CLASS_CHARLIE, + CLASS_PACKAGE_CHARLIE, + OBJECT_CLASS, + INTERFACE_BRAVO, + INTERFACE_CHARLIE, + INTERFACE_ZULU); + } + + @Test + public void interfaceBravo() { + HierarchicalTypeQuery interfaceBravo = INTERFACE_BRAVO.inTypeHierarchy(typeHierarchy); + + assertThat(interfaceBravo.findDirectSuperClass()).isEqualTo(OBJECT_CLASS); + assertThat(interfaceBravo.findDirectSuperInterfaces()).containsExactly(INTERFACE_CHARLIE); + assertThat(interfaceBravo.findDirectSuperTypes()) + .containsExactly(OBJECT_CLASS, INTERFACE_CHARLIE); + + assertThat(interfaceBravo.findTransitiveSuperClasses()).containsExactly(OBJECT_CLASS); + assertThat(interfaceBravo.findTransitiveSuperInterfaces()) + .containsExactly(INTERFACE_CHARLIE, INTERFACE_ZULU); + assertThat(interfaceBravo.findTransitiveSuperTypes()) + .containsExactly(OBJECT_CLASS, INTERFACE_CHARLIE, INTERFACE_ZULU); + } + + @Test + public void classCharlie() { + HierarchicalTypeQuery classCharlie = CLASS_CHARLIE.inTypeHierarchy(typeHierarchy); + + assertThat(classCharlie.findDirectSuperClass()).isEqualTo(CLASS_PACKAGE_CHARLIE); + assertThat(classCharlie.findDirectSuperInterfaces()).containsExactly(INTERFACE_CHARLIE); + assertThat(classCharlie.findDirectSuperTypes()) + .containsExactly(CLASS_PACKAGE_CHARLIE, INTERFACE_CHARLIE); + + assertThat(classCharlie.findTransitiveSuperClasses()) + .containsExactly(CLASS_PACKAGE_CHARLIE, OBJECT_CLASS); + assertThat(classCharlie.findTransitiveSuperInterfaces()) + .containsExactly(INTERFACE_CHARLIE, INTERFACE_ZULU); + assertThat(classCharlie.findTransitiveSuperTypes()) + .containsExactly(CLASS_PACKAGE_CHARLIE, OBJECT_CLASS, INTERFACE_CHARLIE, INTERFACE_ZULU); + } + + @Test + public void interfaceCharlie() { + HierarchicalTypeQuery interfaceCharlie = INTERFACE_CHARLIE.inTypeHierarchy(typeHierarchy); + + assertThat(interfaceCharlie.findDirectSuperClass()).isEqualTo(OBJECT_CLASS); + assertThat(interfaceCharlie.findDirectSuperInterfaces()).containsExactly(INTERFACE_ZULU); + assertThat(interfaceCharlie.findDirectSuperTypes()) + .containsExactly(OBJECT_CLASS, INTERFACE_ZULU); + + assertThat(interfaceCharlie.findTransitiveSuperClasses()).containsExactly(OBJECT_CLASS); + assertThat(interfaceCharlie.findTransitiveSuperInterfaces()).containsExactly(INTERFACE_ZULU); + assertThat(interfaceCharlie.findTransitiveSuperTypes()) + .containsExactly(OBJECT_CLASS, INTERFACE_ZULU); + } + + @Test + public void classZulu() { + HierarchicalTypeQuery classZulu = CLASS_ZULU.inTypeHierarchy(typeHierarchy); + + assertThat(classZulu.findDirectSuperClass()).isEqualTo(CLASS_ALPHA); + assertThat(classZulu.findDirectSuperInterfaces()).isEmpty(); + assertThat(classZulu.findDirectSuperTypes()).containsExactly(CLASS_ALPHA); + + assertThat(classZulu.findTransitiveSuperClasses()) + .containsExactly( + CLASS_ALPHA, CLASS_BRAVO, CLASS_CHARLIE, CLASS_PACKAGE_CHARLIE, OBJECT_CLASS); + assertThat(classZulu.findTransitiveSuperInterfaces()) + .containsExactly(INTERFACE_ALPHA, INTERFACE_BRAVO, INTERFACE_CHARLIE, INTERFACE_ZULU); + assertThat(classZulu.findTransitiveSuperTypes()) + .containsExactly( + CLASS_ALPHA, + CLASS_BRAVO, + CLASS_CHARLIE, + CLASS_PACKAGE_CHARLIE, + OBJECT_CLASS, + INTERFACE_ALPHA, + INTERFACE_BRAVO, + INTERFACE_CHARLIE, + INTERFACE_ZULU); + } + + @Test + public void interfaceZulu() { + HierarchicalTypeQuery classZulu = INTERFACE_ZULU.inTypeHierarchy(typeHierarchy); + + assertThat(classZulu.findDirectSuperClass()).isEqualTo(OBJECT_CLASS); + assertThat(classZulu.findDirectSuperInterfaces()).isEmpty(); + assertThat(classZulu.findDirectSuperTypes()).containsExactly(OBJECT_CLASS); + + assertThat(classZulu.findTransitiveSuperClasses()).containsExactly(OBJECT_CLASS); + assertThat(classZulu.findTransitiveSuperInterfaces()).isEmpty(); + assertThat(classZulu.findTransitiveSuperTypes()).containsExactly(OBJECT_CLASS); + } + + @Test + public void classZulu_getTag_publicVisibility() { + HierarchicalMethodQuery getTag = + HierarchicalMethodKey.from( + MethodKey.create( + /* ownerClass= */ CLASS_ZULU.type(), + /* name= */ "getTag", + /* descriptor= */ "()Ljava/lang/String;")) + .inTypeHierarchy(typeHierarchy); + + assertThat(getTag.getBaseInterfaceMethods().stream().map(HierarchicalMethodKey::owner)) + .containsExactly(INTERFACE_ZULU); + assertThat(getTag.getBaseClassMethods().stream().map(HierarchicalMethodKey::owner)) + .containsExactly(CLASS_ALPHA, CLASS_BRAVO, CLASS_CHARLIE); + assertThat(getTag.getBaseMethods().stream().map(HierarchicalMethodKey::owner)) + .containsExactly(CLASS_ALPHA, CLASS_BRAVO, CLASS_CHARLIE, INTERFACE_ZULU); + } + + @Test + public void classPieAX_twoOperands_packageVisibilityFiltering() { + HierarchicalMethodQuery withTwoOperands = + HierarchicalMethodKey.from( + MethodKey.create( + /* ownerClass= */ PIE_AX.type(), + /* name= */ "withTwoOperands", + /* descriptor= */ "(JJ)J")) + .inTypeHierarchy(typeHierarchy); + + assertThat(withTwoOperands.getBaseClassMethods().stream().map(HierarchicalMethodKey::owner)) + .containsExactly(PIE_BX, PIE_CX); + assertThat(withTwoOperands.getBaseMethods().stream().map(HierarchicalMethodKey::owner)) + .containsExactly(PIE_BX, PIE_CX); + } + + @Test + public void classPieAY_twoOperands_packageVisibilityFiltering() { + HierarchicalMethodQuery withTwoOperands = + HierarchicalMethodKey.from( + MethodKey.create( + /* ownerClass= */ PIE_AY.type(), + /* name= */ "withTwoOperands", + /* descriptor= */ "(JJ)J")) + .inTypeHierarchy(typeHierarchy); + + assertThat(withTwoOperands.getBaseClassMethods().stream().map(HierarchicalMethodKey::owner)) + .containsExactly(PIE_BY, PIE_CY); + assertThat(withTwoOperands.getBaseMethods().stream().map(HierarchicalMethodKey::owner)) + .containsExactly(PIE_BY, PIE_CY); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/ClassZulu.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/ClassZulu.java new file mode 100644 index 00000000000000..5a96a18f343766 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/ClassZulu.java @@ -0,0 +1,33 @@ +/* + * Copyright 2020 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.typehierarchy.testlib; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkga.ClassAlpha; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public class ClassZulu extends ClassAlpha { + + private static final String TAG = "cz"; + + @Override + public String getTag() { + return TAG; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/InterfaceZulu.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/InterfaceZulu.java new file mode 100644 index 00000000000000..249c28650036ec --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/InterfaceZulu.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020 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.typehierarchy.testlib; +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public interface InterfaceZulu { + String getTag(); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkga/ClassAlpha.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkga/ClassAlpha.java new file mode 100644 index 00000000000000..978db9423c0b18 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkga/ClassAlpha.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkga; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.ClassZulu; +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgb.ClassBravo; +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgb.InterfaceBravo; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public class ClassAlpha extends ClassBravo implements InterfaceAlpha, InterfaceBravo { + + private static final String TAG = "ca"; + + @Override + public String getTag() { + return TAG; + } + + @Override + public InterfaceAlpha defaultInstance(ClassZulu instance) { + return instance == null ? this : instance; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkga/InterfaceAlpha.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkga/InterfaceAlpha.java new file mode 100644 index 00000000000000..f53ced652c5251 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkga/InterfaceAlpha.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkga; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgb.InterfaceBravo; +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgc.InterfaceCharlie; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public interface InterfaceAlpha extends InterfaceBravo, InterfaceCharlie {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgb/ClassBravo.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgb/ClassBravo.java new file mode 100644 index 00000000000000..8a336148fa0a72 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgb/ClassBravo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgb; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgc.ClassCharlie; +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgc.InterfaceCharlie; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public abstract class ClassBravo extends ClassCharlie implements InterfaceBravo, InterfaceCharlie { + + private static final String TAG = "cb"; + + @Override + public String getTag() { + return TAG; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgb/InterfaceBravo.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgb/InterfaceBravo.java new file mode 100644 index 00000000000000..c724515a47e9a8 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgb/InterfaceBravo.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgb; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgc.InterfaceCharlie; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public interface InterfaceBravo extends InterfaceCharlie {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/ClassCharlie.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/ClassCharlie.java new file mode 100644 index 00000000000000..9feb947f998711 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/ClassCharlie.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgc; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.ClassZulu; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public abstract class ClassCharlie extends ClassPackageCharlie implements InterfaceCharlie { + + private static final String TAG = "cc"; + + @Override + public String getTag() { + return TAG; + } + + protected InterfaceCharlie defaultInstance(ClassZulu instance) { + return instance == null ? this : instance; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/ClassPackageCharlie.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/ClassPackageCharlie.java new file mode 100644 index 00000000000000..e2f9fdb83ae716 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/ClassPackageCharlie.java @@ -0,0 +1,22 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgc; +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +class ClassPackageCharlie {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/InterfaceCharlie.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/InterfaceCharlie.java new file mode 100644 index 00000000000000..c4067cbc32f783 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgc/InterfaceCharlie.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgc; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.InterfaceZulu; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public interface InterfaceCharlie extends InterfaceZulu {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieAX.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieAX.java new file mode 100644 index 00000000000000..516387b65b3a6f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieAX.java @@ -0,0 +1,28 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgx; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgy.PieAY; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +abstract class PieAX extends PieAY { + @Override + abstract long withTwoOperands(long x, long y); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieBX.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieBX.java new file mode 100644 index 00000000000000..81a0f372b47207 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieBX.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgx; +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public abstract class PieBX extends PieCX { + @Override + abstract long withTwoOperands(long x, long y); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieCX.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieCX.java new file mode 100644 index 00000000000000..a8fdc4d17e16da --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgx/PieCX.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgx; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgy.PieCY; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +abstract class PieCX extends PieCY { + abstract long withTwoOperands(long x, long y); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieAY.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieAY.java new file mode 100644 index 00000000000000..14725d6b8a1d18 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieAY.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgy; +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public abstract class PieAY extends PieBY { + @Override + abstract long withTwoOperands(long x, long y); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieBY.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieBY.java new file mode 100644 index 00000000000000..294dc34a39371e --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieBY.java @@ -0,0 +1,28 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgy; + +import com.google.devtools.build.android.desugar.typehierarchy.testlib.pkgx.PieBX; + +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +abstract class PieBY extends PieBX { + @Override + abstract long withTwoOperands(long x, long y); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieCY.java b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieCY.java new file mode 100644 index 00000000000000..4a23cca92a2920 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typehierarchy/testlib/pkgy/PieCY.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020 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.typehierarchy.testlib.pkgy; +/** + * @see {@link com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyTest} for type + * inheritance structure and dynamic-dispatchable method relationships. + */ +public abstract class PieCY { + abstract long withTwoOperands(long x, long y); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/unused_closed_resource.jar b/src/test/java/com/google/devtools/build/android/desugar/unused_closed_resource.jar new file mode 100644 index 0000000000000000000000000000000000000000..6a451eb046e5cbe09a7793821df8a834f3f3582e GIT binary patch literal 739 zcmWIWW@Zs#;Nak3*xNe6hXDyh1KG*>x%%n(`RO^S`YEYpCHeU|#rjF5nK>!?iFqkS z`I#v|;o{Qt#3KEY)Z&tq#F9k)(7e*()Rf@LypoL6lFVf1oc!X{pw#00(xT*4-_(+f z{1m<9oW$bdt%1JzmmLIZh1CmRsrk+_`^(^@>6A5F$wxKM)nkRwau$_KZJ&?rE{ciC zPgx)R!`1eC5DR;s%#Xm1k7hyD9!EQ;mbv{-yZ2`H%=`6qe^@?n`E#l_+I@CjdpP1} z&|K-YkI%?I57eohcp}7o(nB47*H_Y0B{h3K_)oG4F8X0=D4e?JfyT9&zg8H{UEV0A zVSMott6H&8Zohuo=C0d4qnBl2J`)br*14dbgV}AXopqA=L<)y7JhlEy3p8sBZr)T*?ZCR z%yyS1?aupNs_33f4aI-%%a{scmw7Y(bu30Ts7%g{P%DCvnO}^iG@5QZt-{0u9 z!=?H{bzN_-x6U1f@HsKhoOtCrlDpR%_Ilnp!n}G#cYDQ=$nEz{KDOlYo;%>WIV>^f zw%6)EtO4Ha9A{f6?%&PGz+lA8z!2cg$RxsmNJYqUpj3nkXqct~yjj^mmN5chCy;hw G0`UMUt}Oll literal 0 HcmV?d00001 From 2aa31378aaf94c5ae57b562cedc9f832c694d158 Mon Sep 17 00:00:00 2001 From: Alex Humesky Date: Tue, 11 Jul 2023 19:17:49 -0400 Subject: [PATCH 5/6] Revert "Remove Classic Desugar Tests (with a global exclusion config)" This reverts commit 5767cba4044c2bfd8a4c9596c44d2363630b489d. --- .bazelci/presubmit.yml | 6 + .../devtools/build/android/desugar/BUILD | 6 + .../android/desugar/covariantreturn/BUILD | 49 ++ .../covariantreturn/NioBufferInvocations.java | 59 +++ .../NioBufferRefConverterTest.java | 213 +++++++++ .../devtools/build/android/desugar/nest/BUILD | 234 ++++++++++ .../desugar/nest/ClassFileFormatTest.java | 100 +++++ .../desugar/nest/NestAnalyzerTest.java | 101 +++++ .../nest/NestDesugaringComplexCasesTest.java | 69 +++ .../NestDesugaringConstructorAccessTest.java | 173 +++++++ .../nest/NestDesugaringCoreLibTest.java | 80 ++++ .../nest/NestDesugaringFieldAccessTest.java | 423 ++++++++++++++++++ ...stDesugaringInterfaceMethodAccessTest.java | 152 +++++++ .../nest/NestDesugaringMethodAccessTest.java | 240 ++++++++++ .../android/desugar/nest/NestDigestTest.java | 168 +++++++ .../nest/testsrc/complexcase/Alpha.java | 39 ++ .../desugar/nest/testsrc/complexcase/BUILD | 17 + .../nest/testsrc/complexcase/Bravo.java | 51 +++ .../nest/testsrc/complexcase/Phloem.java | 18 + .../nest/testsrc/complexcase/Xylem.java | 72 +++ .../testsrc/nestanalyzer/AnalyzedTarget.java | 29 ++ .../desugar/nest/testsrc/nestanalyzer/BUILD | 17 + .../testsrc/simpleunit/classfileformat/BUILD | 17 + .../classfileformat/NestOuterInterfaceA.java | 34 ++ .../nest/testsrc/simpleunit/constructor/BUILD | 17 + .../constructor/ConstructorNest.java | 46 ++ .../constructor/DollarSignNamedNest.java | 54 +++ .../simpleunit/core/javadesugar/testing/BUILD | 17 + .../javadesugar/testing/TestCoreType.java | 54 +++ .../nest/testsrc/simpleunit/field/BUILD | 17 + .../testsrc/simpleunit/field/FieldNest.java | 139 ++++++ .../testsrc/simpleunit/interfacemethod/BUILD | 17 + .../interfacemethod/InterfaceNest.java | 129 ++++++ .../nest/testsrc/simpleunit/method/BUILD | 17 + .../testsrc/simpleunit/method/MethodNest.java | 118 +++++ .../testsrc/simpleunit/method/NonNest.java | 39 ++ .../devtools/build/android/desugar/scan/BUILD | 36 ++ .../android/desugar/scan/test_keep_scanner.sh | 27 ++ .../scan/testdata/CollectionReferences.java | 64 +++ .../OverlappingCollectionReferences.java | 49 ++ .../android/desugar/scan/testdata_golden.txt | 62 +++ .../build/android/desugar/stringconcat/BUILD | 59 +++ .../IndyStringConcatDesugaringlTest.java | 320 +++++++++++++ .../stringconcat/StringConcatTestCases.java | 106 +++++ .../build/android/desugar/testing/junit/BUILD | 69 +++ .../testing/junit/DesugarRuleTest.java | 236 ++++++++++ .../junit/DesugarRuleTestSourceTarget.java | 32 ++ .../testing/junit/DesugarRuleTestTarget.java | 53 +++ .../typeannotation/AnnotationUser.java | 63 +++ .../android/desugar/typeannotation/BUILD | 48 ++ .../LocalTypeAnnotationUseTest.java | 97 ++++ 51 files changed, 4352 insertions(+) create mode 100644 src/test/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferInvocations.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverterTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/ClassFileFormatTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/NestAnalyzerTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringComplexCasesTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringConstructorAccessTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringCoreLibTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringFieldAccessTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringInterfaceMethodAccessTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringMethodAccessTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/NestDigestTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Alpha.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Bravo.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Phloem.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Xylem.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer/AnalyzedTarget.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat/NestOuterInterfaceA.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/ConstructorNest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/DollarSignNamedNest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/core/javadesugar/testing/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/core/javadesugar/testing/TestCoreType.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field/FieldNest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod/InterfaceNest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/MethodNest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/NonNest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/scan/BUILD create mode 100755 src/test/java/com/google/devtools/build/android/desugar/scan/test_keep_scanner.sh create mode 100644 src/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/scan/testdata/OverlappingCollectionReferences.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt create mode 100644 src/test/java/com/google/devtools/build/android/desugar/stringconcat/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/stringconcat/IndyStringConcatDesugaringlTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/stringconcat/StringConcatTestCases.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testing/junit/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTest.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTestSourceTarget.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTestTarget.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typeannotation/AnnotationUser.java create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typeannotation/BUILD create mode 100644 src/test/java/com/google/devtools/build/android/desugar/typeannotation/LocalTypeAnnotationUseTest.java diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index f33e45d8bf067a..7485e3ec7d1e05 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -265,6 +265,12 @@ tasks: - "//tools/test/..." # Re-enable the following tests on Windows: # https://github.com/bazelbuild/bazel/issues/4292 + - "-//src/test/java/com/google/devtools/build/android/desugar/nest/..." + - "-//src/test/java/com/google/devtools/build/android/desugar/stringconcat/..." + - "-//src/test/java/com/google/devtools/build/android/desugar/testing/junit/..." + - "-//src/test/java/com/google/devtools/build/android/desugar/covariantreturn/..." + - "-//src/test/java/com/google/devtools/build/android/desugar/scan/..." + - "-//src/test/java/com/google/devtools/build/android/desugar/typeannotation/..." - "-//src/test/java/com/google/devtools/build/android/r8/..." - "-//src/test/java/com/google/devtools/build/lib/query2/cquery/..." - "-//src/test/java/com/google/devtools/build/lib/query2/engine/..." diff --git a/src/test/java/com/google/devtools/build/android/desugar/BUILD b/src/test/java/com/google/devtools/build/android/desugar/BUILD index 96480813e94a69..97f9e155ef4ab8 100644 --- a/src/test/java/com/google/devtools/build/android/desugar/BUILD +++ b/src/test/java/com/google/devtools/build/android/desugar/BUILD @@ -17,7 +17,13 @@ filegroup( "//src/test/java/com/google/devtools/build/android/desugar/dependencies:srcs", "//src/test/java/com/google/devtools/build/android/desugar/io:srcs", "//src/test/java/com/google/devtools/build/android/desugar/langmodel:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/nest:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/covariantreturn:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/typeannotation:srcs", "//src/test/java/com/google/devtools/build/android/desugar/runtime:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/scan:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/stringconcat:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/testing/junit:srcs", ], visibility = ["//src/test/java/com/google/devtools/build/android:__pkg__"], ) diff --git a/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD new file mode 100644 index 00000000000000..4308ccb07845d8 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD @@ -0,0 +1,49 @@ +load("@rules_java//java:defs.bzl", "java_test") + +# Description: +# Tests for the Java 8 desugaring tool for Android. +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +java_test( + name = "NioBufferRefConverterTest", + size = "medium", + srcs = ["NioBufferRefConverterTest.java"], + data = [ + ":nio_buffer_invocations_src", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_srcs='$(locations :nio_buffer_invocations_src)'", + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + ], + test_class = "com.google.devtools.build.android.desugar.covariantreturn.NioBufferRefConverterTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:guava-testlib", + "//third_party:jsr330_inject", + "//third_party:junit4", + "//third_party:truth", + ], +) + +filegroup( + name = "nio_buffer_invocations_src", + srcs = ["NioBufferInvocations.java"], +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["**"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferInvocations.java b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferInvocations.java new file mode 100644 index 00000000000000..029b854e4b6f25 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferInvocations.java @@ -0,0 +1,59 @@ +/* + * Copyright 2020 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.covariantreturn; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.nio.ShortBuffer; + +/** Test source for {@link NioBufferRefConverterTest}. */ +public final class NioBufferInvocations { + + public static IntBuffer getIntBufferPosition(IntBuffer buffer, int position) { + return buffer.position(position); + } + + public static CharBuffer getCharBufferPosition(CharBuffer buffer, int position) { + return buffer.position(position); + } + + public static FloatBuffer getFloatBufferPosition(FloatBuffer buffer, int position) { + return buffer.position(position); + } + + public static DoubleBuffer getDoubleBufferPosition(DoubleBuffer buffer, int position) { + return buffer.position(position); + } + + public static ShortBuffer getShortBufferPosition(ShortBuffer buffer, int position) { + return buffer.position(position); + } + + public static LongBuffer getLongBufferPosition(LongBuffer buffer, int position) { + return buffer.position(position); + } + + public static ByteBuffer getByteBufferPosition(ByteBuffer buffer, int position) { + return buffer.position(position); + } + + private NioBufferInvocations() {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverterTest.java b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverterTest.java new file mode 100644 index 00000000000000..53054039cf9386 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverterTest.java @@ -0,0 +1,213 @@ +/* + * Copyright 2020 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.covariantreturn; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.nio.ShortBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; + +/** Functional Tests for {@link NioBufferRefConverter}. */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public class NioBufferRefConverterTest { + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, MethodHandles.lookup()) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .addCommandOptions("desugar_nest_based_private_access", "true") + .addCommandOptions("allow_empty_bootclasspath", "true") + .addCommandOptions("core_library", "true") + .setWorkingJavaPackage("com.google.devtools.build.android.desugar.covariantreturn") + .build(); + + @Test + public void methodOfNioBufferWithCovariantTypes_beforeDesugar( + @AsmNode(className = "NioBufferInvocations", memberName = "getByteBufferPosition", round = 0) + MethodNode before) { + ImmutableList methodInvocations = + Arrays.stream(before.instructions.toArray()) + .filter(insnNode -> insnNode.getType() == METHOD_INSN) + .collect(toImmutableList()); + + assertThat(methodInvocations).hasSize(1); + MethodInsnNode methodInsnNode = (MethodInsnNode) Iterables.getOnlyElement(methodInvocations); + + assertThat(methodInsnNode.owner).isEqualTo("java/nio/ByteBuffer"); + assertThat(methodInsnNode.name).isEqualTo("position"); + assertThat(methodInsnNode.desc).isEqualTo("(I)Ljava/nio/ByteBuffer;"); + + assertThat(methodInsnNode.getNext().getOpcode()).isEqualTo(Opcodes.ARETURN); + } + + @Test + public void methodOfNioBufferWithCovariantTypes_afterDesugar( + @AsmNode(className = "NioBufferInvocations", memberName = "getByteBufferPosition", round = 1) + MethodNode after) { + ImmutableList methodInvocations = + Arrays.stream(after.instructions.toArray()) + .filter(insnNode -> insnNode.getType() == METHOD_INSN) + .collect(toImmutableList()); + + assertThat(methodInvocations).hasSize(1); + MethodInsnNode methodInsnNode = (MethodInsnNode) Iterables.getOnlyElement(methodInvocations); + + assertThat(methodInsnNode.owner).isEqualTo("java/nio/ByteBuffer"); + assertThat(methodInsnNode.name).isEqualTo("position"); + assertThat(methodInsnNode.desc).isEqualTo("(I)Ljava/nio/Buffer;"); + + TypeInsnNode typeInsnNode = (TypeInsnNode) methodInsnNode.getNext(); + assertThat(typeInsnNode.getOpcode()).isEqualTo(Opcodes.CHECKCAST); + assertThat(typeInsnNode.desc).isEqualTo("java/nio/ByteBuffer"); + + assertThat(typeInsnNode.getNext().getOpcode()).isEqualTo(Opcodes.ARETURN); + } + + @Test + public void methodOfNioBufferWithCovariantTypes_beforeDesugarInvocation( + @RuntimeMethodHandle( + className = "NioBufferInvocations", + memberName = "getByteBufferPosition", + round = 0) + MethodHandle before) + throws Throwable { + ByteBuffer buffer = ByteBuffer.wrap("random text".getBytes(Charset.defaultCharset())); + int expectedPos = 2; + + ByteBuffer result = (ByteBuffer) before.invoke(buffer, expectedPos); + assertThat(result.position()).isEqualTo(expectedPos); + } + + @Test + public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfByteBufferMethod( + @RuntimeMethodHandle(className = "NioBufferInvocations", memberName = "getByteBufferPosition") + MethodHandle after) + throws Throwable { + ByteBuffer buffer = ByteBuffer.wrap("random text".getBytes(Charset.defaultCharset())); + int expectedPos = 2; + + ByteBuffer result = (ByteBuffer) after.invoke(buffer, expectedPos); + assertThat(result.position()).isEqualTo(expectedPos); + } + + @Test + public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfCharBufferMethod( + @RuntimeMethodHandle(className = "NioBufferInvocations", memberName = "getCharBufferPosition") + MethodHandle after) + throws Throwable { + CharBuffer buffer = CharBuffer.wrap("random text".toCharArray()); + int expectedPos = 2; + + CharBuffer result = (CharBuffer) after.invoke(buffer, expectedPos); + assertThat(result.position()).isEqualTo(expectedPos); + } + + @Test + public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfIntBufferMethod( + @RuntimeMethodHandle(className = "NioBufferInvocations", memberName = "getIntBufferPosition") + MethodHandle after) + throws Throwable { + IntBuffer buffer = IntBuffer.wrap(new int[] {10, 20, 30}); + int expectedPos = 2; + + IntBuffer result = (IntBuffer) after.invoke(buffer, expectedPos); + assertThat(result.position()).isEqualTo(expectedPos); + } + + @Test + public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfFloatBufferMethod( + @RuntimeMethodHandle( + className = "NioBufferInvocations", + memberName = "getFloatBufferPosition") + MethodHandle after) + throws Throwable { + FloatBuffer buffer = FloatBuffer.wrap(new float[] {10f, 20f, 30f}); + int expectedPos = 2; + + FloatBuffer result = (FloatBuffer) after.invoke(buffer, expectedPos); + assertThat(result.position()).isEqualTo(expectedPos); + } + + @Test + public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfDoubleBufferMethod( + @RuntimeMethodHandle( + className = "NioBufferInvocations", + memberName = "getDoubleBufferPosition") + MethodHandle after) + throws Throwable { + DoubleBuffer buffer = DoubleBuffer.wrap(new double[] {10.0, 20.0, 30.0}); + int expectedPos = 2; + + DoubleBuffer result = (DoubleBuffer) after.invoke(buffer, expectedPos); + assertThat(result.position()).isEqualTo(expectedPos); + } + + @Test + public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfShortBufferMethod( + @RuntimeMethodHandle( + className = "NioBufferInvocations", + memberName = "getShortBufferPosition") + MethodHandle after) + throws Throwable { + ShortBuffer buffer = ShortBuffer.wrap(new short[] {10, 20, 30}); + int expectedPos = 2; + + ShortBuffer result = (ShortBuffer) after.invoke(buffer, expectedPos); + assertThat(result.position()).isEqualTo(expectedPos); + } + + @Test + public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfLongBufferMethod( + @RuntimeMethodHandle(className = "NioBufferInvocations", memberName = "getLongBufferPosition") + MethodHandle after) + throws Throwable { + LongBuffer buffer = LongBuffer.wrap(new long[] {10L, 20L, 30L}); + int expectedPos = 2; + + LongBuffer result = (LongBuffer) after.invoke(buffer, expectedPos); + assertThat(result.position()).isEqualTo(expectedPos); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/BUILD b/src/test/java/com/google/devtools/build/android/desugar/nest/BUILD new file mode 100644 index 00000000000000..a92d62fc6c989c --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/BUILD @@ -0,0 +1,234 @@ +load("@rules_java//java:defs.bzl", "java_test") + +# Description: +# Tests for the Java 8 desugaring tool for Android. +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +java_test( + name = "ClassFileFormatTest", + size = "medium", + srcs = ["ClassFileFormatTest.java"], + data = [ + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_srcs='$(locations //src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat)'", + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + ], + test_class = "com.google.devtools.build.android.desugar.nest.ClassFileFormatTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:guava-testlib", + "//third_party:jsr330_inject", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "NestAnalyzerTest", + size = "medium", + srcs = ["NestAnalyzerTest.java"], + data = [ + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer:analyzed_target", + ], + jvm_flags = [ + "-Dinput_srcs=$(location //src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer:analyzed_target)", + ], + test_class = "com.google.devtools.build.android.desugar.nest.NestAnalyzerTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//src/tools/android/java/com/google/devtools/build/android/desugar/nest", + "//src/tools/android/java/com/google/devtools/build/android/desugar/preanalysis", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "NestDigestTest", + size = "small", + srcs = ["NestDigestTest.java"], + test_class = "com.google.devtools.build.android.desugar.nest.NestDigestTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//src/tools/android/java/com/google/devtools/build/android/desugar/nest", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "NestDesugaringConstructorAccessTest", + size = "medium", + srcs = ["NestDesugaringConstructorAccessTest.java"], + data = [ + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_srcs='$(locations //src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor)'", + # Required by Desugar#verifyLambdaDumpDirectoryRegistered + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + ], + test_class = "com.google.devtools.build.android.desugar.nest.NestDesugaringConstructorAccessTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:jsr330_inject", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "NestDesugaringFieldAccessTest", + size = "medium", + srcs = ["NestDesugaringFieldAccessTest.java"], + data = [ + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_srcs=$(location //src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field)", + # Required by Desugar#verifyLambdaDumpDirectoryRegistered + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + ], + test_class = "com.google.devtools.build.android.desugar.nest.NestDesugaringFieldAccessTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:jsr330_inject", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "NestDesugaringInterfaceMethodAccessTest", + size = "medium", + srcs = ["NestDesugaringInterfaceMethodAccessTest.java"], + data = [ + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_srcs=$(location //src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod)", + # Required by Desugar#verifyLambdaDumpDirectoryRegistered + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Djava.lang.invoke.stringConcat=MH_INLINE_SIZED_EXACT", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + ], + test_class = "com.google.devtools.build.android.desugar.nest.NestDesugaringInterfaceMethodAccessTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:jsr330_inject", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "NestDesugaringMethodAccessTest", + size = "medium", + srcs = ["NestDesugaringMethodAccessTest.java"], + data = [ + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_srcs='$(locations //src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method)'", + # Required by Desugar#verifyLambdaDumpDirectoryRegistered + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Djava.lang.invoke.stringConcat=MH_INLINE_SIZED_EXACT", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + ], + test_class = "com.google.devtools.build.android.desugar.nest.NestDesugaringMethodAccessTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:jsr330_inject", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "NestDesugaringComplexCasesTest", + size = "medium", + srcs = ["NestDesugaringComplexCasesTest.java"], + data = [ + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_srcs='$(locations //src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase)'", + # Required by Desugar#verifyLambdaDumpDirectoryRegistered + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + ], + test_class = "com.google.devtools.build.android.desugar.nest.NestDesugaringComplexCasesTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]) + [ + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/core/javadesugar/testing:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod:srcs", + "//src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method:srcs", + ], +) + +test_suite( + name = "AllTests", +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/ClassFileFormatTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/ClassFileFormatTest.java new file mode 100644 index 00000000000000..6a89d951eff6b5 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/ClassFileFormatTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020 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.nest; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import java.lang.invoke.MethodHandles; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.tree.ClassNode; + +/** + * Tests for accessing a series of private fields, constructors and methods from another class + * within a nest. + */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public final class ClassFileFormatTest { + + private static final MethodHandles.Lookup lookup = MethodHandles.lookup(); + + @Rule + @SuppressWarnings("SplitterToStream") // Pending bazel guava update. + public final DesugarRule desugarRule = + DesugarRule.builder(this, lookup) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .setWorkingJavaPackage( + "com.google.devtools.build.android.desugar.nest.testsrc.simpleunit.classfileformat") + .addCommandOptions("desugar_nest_based_private_access", "true") + .build(); + + @Test + public void classFileMajorVersions( + @AsmNode(className = "NestOuterInterfaceA", round = 0) ClassNode beforeDesugarClassNode, + @AsmNode(className = "NestOuterInterfaceA", round = 1) ClassNode afterDesugarClassNode) { + assertThat(beforeDesugarClassNode.version).isEqualTo(JdkVersion.V11); + assertThat(afterDesugarClassNode.version).isEqualTo(JdkVersion.V1_7); + } + + @Test + public void nestMembersAttribute_strippedOutAfterDesugaring( + @AsmNode(className = "NestOuterInterfaceA", round = 0) ClassNode before, + @AsmNode(className = "NestOuterInterfaceA", round = 1) ClassNode after) { + assertThat(before.nestMembers).isNotEmpty(); + assertThat(after.nestMembers).isNull(); + } + + @Test + public void nestHostAttribute_strippedOutAfterDesugaringForNestedClass( + @AsmNode(className = "NestOuterInterfaceA$NestedClassB", round = 0) ClassNode before, + @AsmNode(className = "NestOuterInterfaceA$NestedClassB", round = 1) ClassNode after) { + assertThat(before.nestHostClass).isNotEmpty(); + assertThat(after.nestHostClass).isNull(); + } + + @Test + public void nestHostAttribute_strippedOutAfterDesugaringForNestedInterface( + @AsmNode(className = "NestOuterInterfaceA$NestedInterfaceC", round = 0) ClassNode before, + @AsmNode(className = "NestOuterInterfaceA$NestedInterfaceC", round = 1) ClassNode after) { + assertThat(before.nestHostClass).isNotEmpty(); + assertThat(after.nestHostClass).isNull(); + } + + @Test + public void nestHostAttribute_strippedOutAfterDesugaringForNestedAnnotation( + @AsmNode(className = "NestOuterInterfaceA$NestedAnnotationD", round = 0) ClassNode before, + @AsmNode(className = "NestOuterInterfaceA$NestedAnnotationD", round = 1) ClassNode after) { + assertThat(before.nestHostClass).isNotEmpty(); + assertThat(after.nestHostClass).isNull(); + } + + @Test + public void nestHostAttribute_strippedOutAfterDesugaringForNestedEnum( + @AsmNode(className = "NestOuterInterfaceA$NestedEnumE", round = 0) ClassNode before, + @AsmNode(className = "NestOuterInterfaceA$NestedEnumE", round = 1) ClassNode after) { + assertThat(before.nestHostClass).isNotEmpty(); + assertThat(after.nestHostClass).isNull(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestAnalyzerTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestAnalyzerTest.java new file mode 100644 index 00000000000000..1ef8e5f5f01192 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestAnalyzerTest.java @@ -0,0 +1,101 @@ +// Copyright 2019 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.nest; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.io.FileContentProvider; +import com.google.devtools.build.android.desugar.io.JarItem; +import com.google.devtools.build.android.desugar.preanalysis.InputPreAnalyzer; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeJarEntry; +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandles; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.Attribute; + +/** The tests for {@link NestAnalyzer}. */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public final class NestAnalyzerTest { + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, MethodHandles.lookup()) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .setWorkingJavaPackage( + "com.google.devtools.build.android.desugar.nest.testsrc.nestanalyzer") + .enableIterativeTransformation(0) + .build(); + + @Test + public void emptyInputFiles() throws IOException { + InputPreAnalyzer inputPreAnalyzer = + new InputPreAnalyzer(/* inputFileContents= */ ImmutableList.of(), new Attribute[] {}); + inputPreAnalyzer.process(); + NestDigest nestDigest = + NestAnalyzer.digest( + inputPreAnalyzer.getClassAttributeRecord(), inputPreAnalyzer.getClassMemberRecord()); + + assertThat(nestDigest.getAllCompanionClassNames()).isEmpty(); + } + + @Test + public void companionClassGeneration( + @RuntimeJarEntry( + value = "AnalyzedTarget.class", + round = 0) // Without desugaring at zero-th round. + JarItem analyzedTarget) + throws IOException { + JarFile jarFile = analyzedTarget.jarFile(); + + InputPreAnalyzer inputPreAnalyzer = + new InputPreAnalyzer( + jarFile.stream() + .map( + entry -> + new FileContentProvider<>( + entry.getName(), () -> getJarEntryInputStream(jarFile, entry))) + .collect(toImmutableList()), + new Attribute[] {}); + inputPreAnalyzer.process(); + NestDigest nestDigest = + NestAnalyzer.digest( + inputPreAnalyzer.getClassAttributeRecord(), inputPreAnalyzer.getClassMemberRecord()); + + assertThat(nestDigest.getAllCompanionClassNames()) + .containsExactly( + "com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer/AnalyzedTarget$NestCC"); + } + + private static InputStream getJarEntryInputStream(JarFile jarFile, JarEntry entry) { + try { + return jarFile.getInputStream(entry); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringComplexCasesTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringComplexCasesTest.java new file mode 100644 index 00000000000000..97e1408884bf5f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringComplexCasesTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 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.nest; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.tree.ClassNode; + +/** + * Tests for accessing a series of private fields, constructors and methods from another class + * within a nest. + */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public final class NestDesugaringComplexCasesTest { + + private static final MethodHandles.Lookup lookup = MethodHandles.lookup(); + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, lookup) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .setWorkingJavaPackage( + "com.google.devtools.build.android.desugar.nest.testsrc.complexcase") + .addCommandOptions("desugar_nest_based_private_access", "true") + .build(); + + @Test + public void inputClassFileMajorVersions( + @AsmNode(className = "Xylem", round = 0) ClassNode beforeDesugarClassNode, + @AsmNode(className = "Xylem", round = 1) ClassNode afterDesugarClassNode) { + assertThat(beforeDesugarClassNode.version).isEqualTo(JdkVersion.V11); + assertThat(afterDesugarClassNode.version).isEqualTo(JdkVersion.V1_7); + } + + @Test + public void comprehensiveTest( + @RuntimeMethodHandle(className = "Xylem", memberName = "execute") MethodHandle xylemExecute) + throws Throwable { + long result = (long) xylemExecute.invoke((long) 2L, (int) 3); + assertThat(result).isEqualTo(14004004171L); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringConstructorAccessTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringConstructorAccessTest.java new file mode 100644 index 00000000000000..33a258b1640068 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringConstructorAccessTest.java @@ -0,0 +1,173 @@ +/* + * Copyright 2019 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.nest; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.DynamicClassLiteral; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +/** Tests for accessing private constructors from another class within a nest. */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public final class NestDesugaringConstructorAccessTest { + + private static final Lookup lookup = MethodHandles.lookup(); + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, lookup) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .setWorkingJavaPackage( + "com.google.devtools.build.android.desugar.nest.testsrc.simpleunit.constructor") + .addCommandOptions("desugar_nest_based_private_access", "true") + .build(); + + @Inject + @DynamicClassLiteral("ConstructorNest$ConstructorServiceMate") + private Class mate; + + @Inject + @DynamicClassLiteral("ConstructorNest") + private Class invoker; + + @Test + public void inputClassFileMajorVersions( + @AsmNode(className = "ConstructorNest", round = 0) ClassNode beforeDesugarClassNode, + @AsmNode(className = "ConstructorNest", round = 1) ClassNode afterDesugarClassNode) { + assertThat(beforeDesugarClassNode.version).isEqualTo(JdkVersion.V11); + assertThat(afterDesugarClassNode.version).isEqualTo(JdkVersion.V1_7); + } + + @Test + public void companionClassIsPresent( + @DynamicClassLiteral("ConstructorNest$NestCC") Class companion) { + assertThat(companion).isNotNull(); + } + + @Test + public void companionClassHierarchy( + @DynamicClassLiteral("ConstructorNest$NestCC") Class companion) { + assertThat(companion.getEnclosingClass()).isEqualTo(invoker); + assertThat(companion.getEnclosingConstructor()).isNull(); + assertThat(companion.getEnclosingMethod()).isNull(); + } + + @Test + public void companionClassModifiers( + @DynamicClassLiteral("ConstructorNest$NestCC") Class companion) { + assertThat(companion.isSynthetic()).isTrue(); + assertThat(companion.isMemberClass()).isTrue(); + assertThat(Modifier.isAbstract(companion.getModifiers())).isTrue(); + assertThat(Modifier.isStatic(companion.getModifiers())).isTrue(); + } + + @Test + public void constructorBridgeGeneration() { + assertThat(mate.getDeclaredConstructors()).hasLength(4); + } + + @Test + public void zeroArgConstructorBridge( + @DynamicClassLiteral("ConstructorNest$NestCC") Class companion) throws Exception { + Constructor constructor = mate.getDeclaredConstructor(companion); + assertThat(constructor.getModifiers() & 0x7).isEqualTo(0); + } + + @Test + public void multiArgConstructorBridge( + @DynamicClassLiteral("ConstructorNest$NestCC") Class companion) throws Exception { + Constructor constructor = mate.getDeclaredConstructor(long.class, int.class, companion); + + assertThat(Modifier.isPublic(constructor.getModifiers())).isFalse(); + assertThat(Modifier.isPrivate(constructor.getModifiers())).isFalse(); + assertThat(Modifier.isProtected(constructor.getModifiers())).isFalse(); + } + + @Test + public void createFromEmptyArgConstructor( + @RuntimeMethodHandle( + className = "ConstructorNest", + memberName = "createFromZeroArgConstructor") + MethodHandle createFromZeroArgConstructor) + throws Throwable { + long result = (long) createFromZeroArgConstructor.invoke(); + assertThat(result).isEqualTo(30L); + } + + @Test + public void createFromMultiArgConstructor( + @RuntimeMethodHandle( + className = "ConstructorNest", + memberName = "createFromMultiArgConstructor") + MethodHandle createFromMultiArgConstructor) + throws Throwable { + long result = (long) createFromMultiArgConstructor.invoke((long) 20L, (int) 30); + assertThat(result).isEqualTo(50L); + } + + @Test + public void nestWithDollarSignNamedClasses_nestHostSyntheticConstructor( + @AsmNode( + className = "$Dollar$Sign$Named$Nest$", + memberName = "", + memberDescriptor = + "(JLcom/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/$Dollar$Sign$Named$Nest$$NestCC;)V") + MethodNode constructor) + throws Throwable { + assertThat(constructor).isNotNull(); + } + + @Test + public void nestWithDollarSignNamedClasses_nestMemberSyntheticConstructor( + @AsmNode( + className = "$Dollar$Sign$Named$Nest$$$Dollar$Sign$Named$Member$", + memberName = "", + memberDescriptor = + "(Lcom/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/$Dollar$Sign$Named$Nest$;Lcom/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/$Dollar$Sign$Named$Nest$$NestCC;)V") + MethodNode constructor) + throws Throwable { + assertThat(constructor).isNotNull(); + } + + @Test + public void nestWithDollarSignNamedClasses_execute( + @RuntimeMethodHandle(className = "DollarSignNamedNest", memberName = "execute") + MethodHandle execute) + throws Throwable { + + long result = (long) execute.invoke((long) 10L); + assertThat(result).isEqualTo(14L); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringCoreLibTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringCoreLibTest.java new file mode 100644 index 00000000000000..1e5932b7495d79 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringCoreLibTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2020 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.nest; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.android.desugar.testing.junit.DesugarTestHelpers.getRuntimePathsFromJvmFlag; + +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.tree.ClassNode; + +/** Tests for accessing private constructors from another class within a nest. */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public class NestDesugaringCoreLibTest { + + private static final Lookup lookup = MethodHandles.lookup(); + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, lookup) + .addSourceInputs(getRuntimePathsFromJvmFlag("input_srcs")) + .addJavacOptions("--release 11") + .addCommandOptions("desugar_nest_based_private_access", "true") + .addCommandOptions("allow_empty_bootclasspath", "true") + .addCommandOptions("core_library", "true") + .addCommandOptions("desugar_supported_core_libs", "true") + .addCommandOptions("rewrite_core_library_prefix", "javadesugar/testing/") + .build(); + + @Test + public void inputClassFileMajorVersions( + @AsmNode(className = "javadesugar.testing.TestCoreType$MateA", round = 0) ClassNode before, + @AsmNode(className = "jd$.testing.TestCoreType$MateA", round = 1) ClassNode after) { + assertThat(before.version).isEqualTo(JdkVersion.V11); + assertThat(after.version).isEqualTo(JdkVersion.V1_7); + } + + @Test + public void invokeInterMatePrivateStaticMethodOfCoreLibType( + @RuntimeMethodHandle(className = "jd$.testing.TestCoreType", memberName = "twoSum") + MethodHandle twoSum) + throws Throwable { + long result = (long) twoSum.invoke(1L, 2L); + assertThat(result).isEqualTo(3L); + } + + @Test + public void invokeInterMatePrivateInstanceMethodOfCoreLibType( + @RuntimeMethodHandle(className = "jd$.testing.TestCoreType", memberName = "twoSumWithBase") + MethodHandle twoSum) + throws Throwable { + long result = (long) twoSum.invoke(1000L, 1L, 2L); + assertThat(result).isEqualTo(1003L); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringFieldAccessTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringFieldAccessTest.java new file mode 100644 index 00000000000000..dab87aebe60c0c --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringFieldAccessTest.java @@ -0,0 +1,423 @@ +/* + * Copyright 2019 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.nest; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.DynamicClassLiteral; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.inject.Inject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +/** Tests for accessing private fields from another class within a nest. */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public final class NestDesugaringFieldAccessTest { + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, MethodHandles.lookup()) + .addSourceInputsFromJvmFlag("input_srcs") + .setWorkingJavaPackage( + "com.google.devtools.build.android.desugar.nest.testsrc.simpleunit.field") + .addJavacOptions("--release 11") + .addCommandOptions("desugar_nest_based_private_access", "true") + .build(); + + @Inject + @DynamicClassLiteral("FieldNest$FieldOwnerMate") + private Class mate; + + private Object mateInstance; + + @Before + public void loadClasses() throws Exception { + mateInstance = mate.getConstructor().newInstance(); + } + + @Test + public void inputClassFileMajorVersions( + @AsmNode(className = "FieldNest", round = 0) ClassNode beforeDesugarClassNode, + @AsmNode(className = "FieldNest", round = 1) ClassNode afterDesugarClassNode) { + assertThat(beforeDesugarClassNode.version).isEqualTo(JdkVersion.V11); + assertThat(afterDesugarClassNode.version).isEqualTo(JdkVersion.V1_7); + } + + @Test + public void bridgeMethodGeneration() { + List bridgeMethodNames = + Arrays.stream(mate.getDeclaredMethods()) + .map(Method::getName) + .filter(name -> !name.startsWith("$jacoco")) + .collect(Collectors.toList()); + assertThat(bridgeMethodNames) + .containsExactly( + "privateStaticField$bridge_getter", + "privateStaticField$bridge_setter", + "privateInstanceField$bridge_getter", + "privateInstanceField$bridge_setter", + "privateInstanceWideField$bridge_setter", + "getPrivateStaticFieldInBoundary", + "getPrivateInstanceFieldInBoundary", + "privateStaticFieldReadOnly$bridge_getter", + "privateInstanceFieldReadOnly$bridge_getter", + "privateStaticArrayField$bridge_getter", + "privateInstanceArrayField$bridge_getter"); + } + + @Test + public void getStaticField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "getStaticField") + MethodHandle getStaticField) + throws Throwable { + long result = (long) getStaticField.invoke(); + assertThat(result).isEqualTo(10L); + } + + @Test + public void getInstanceField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "getInstanceField") + MethodHandle getInstanceField) + throws Throwable { + long result = (long) getInstanceField.invoke(mateInstance); + assertThat(result).isEqualTo(20); + } + + @Test + public void getPrivateStaticField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "getPrivateStaticField") + MethodHandle getPrivateStaticField) + throws Throwable { + long result = (long) getPrivateStaticField.invoke(); + assertThat(result).isEqualTo(30L); + } + + @Test + public void setPrivateStaticField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "setPrivateStaticField") + MethodHandle setPrivateStaticField) + throws Throwable { + long result = (long) setPrivateStaticField.invoke((long) 35L); + assertThat(result).isEqualTo(35L); + + Field privateStaticField = mate.getDeclaredField("privateStaticField"); + privateStaticField.setAccessible(true); + assertThat(privateStaticField.get(null)).isEqualTo(35L); + } + + @Test + public void getPrivateInstanceField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "getPrivateInstanceField") + MethodHandle getPrivateInstanceField) + throws Throwable { + int result = (int) getPrivateInstanceField.invoke(mateInstance); + assertThat(result).isEqualTo(40); + } + + @Test + public void setPrivateInstanceField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "setPrivateInstanceField") + MethodHandle setPrivateInstanceField) + throws Throwable { + int result = (int) setPrivateInstanceField.invoke(mateInstance, (int) 45); + assertThat(result).isEqualTo(45); + + Field privateInstanceField = mate.getDeclaredField("privateInstanceField"); + privateInstanceField.setAccessible(true); + assertThat(privateInstanceField.get(mateInstance)).isEqualTo(45); + } + + @Test + public void setPrivateInstanceWideField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "setPrivateInstanceWideField") + MethodHandle setPrivateInstanceWideField) + throws Throwable { + long result = (long) setPrivateInstanceWideField.invoke(mateInstance, 47L); + assertThat(result).isEqualTo(47L); + + Field privateInstanceField = mate.getDeclaredField("privateInstanceWideField"); + privateInstanceField.setAccessible(true); + assertThat(privateInstanceField.get(mateInstance)).isEqualTo(47L); + } + + @Test + public void setPrivateInstanceWideField_opcodes( + @AsmNode(className = "FieldNest", memberName = "setPrivateInstanceWideField") + MethodNode setPrivateInstanceWideField) + throws Throwable { + AbstractInsnNode[] instructions = setPrivateInstanceWideField.instructions.toArray(); + assertThat(Arrays.stream(instructions).map(AbstractInsnNode::getOpcode)) + .containsAtLeast( + Opcodes.ALOAD, + Opcodes.LLOAD, + Opcodes.DUP2_X1, + Opcodes.INVOKESTATIC, + Opcodes.POP2, + Opcodes.LRETURN) + .inOrder(); + } + + @Test + public void getPrivateStaticFieldReadOnly( + @RuntimeMethodHandle(className = "FieldNest", memberName = "getPrivateStaticFieldReadOnly") + MethodHandle getPrivateStaticFieldReadOnly) + throws Throwable { + long result = (long) getPrivateStaticFieldReadOnly.invoke(); + assertThat(result).isEqualTo(50L); + } + + @Test + public void getPrivateInstanceFieldReadOnly( + @RuntimeMethodHandle(className = "FieldNest", memberName = "getPrivateInstanceFieldReadOnly") + MethodHandle getPrivateInstanceFieldReadOnly) + throws Throwable { + long result = (long) getPrivateInstanceFieldReadOnly.invoke(mateInstance); + assertThat(result).isEqualTo(60L); + } + + @Test + public void getPrivateInstanceFieldReadOnly_trace( + @RuntimeMethodHandle(className = "FieldNest", memberName = "getPrivateInstanceFieldReadOnly") + MethodHandle getPrivateInstanceFieldReadOnly) + throws Throwable { + long result = (long) getPrivateInstanceFieldReadOnly.invoke(mateInstance); + assertThat(result).isEqualTo(60L); + } + + @Test + public void getPrivateStaticFieldInBoundary( + @RuntimeMethodHandle(className = "FieldNest", memberName = "getPrivateStaticFieldInBoundary") + MethodHandle getPrivateStaticFieldInBoundary) + throws Throwable { + long result = (long) getPrivateStaticFieldInBoundary.invoke(); + assertThat(result).isEqualTo(70L); + } + + @Test + public void getPrivateInstanceFieldInBoundary( + @RuntimeMethodHandle( + className = "FieldNest", + memberName = "getPrivateInstanceFieldInBoundary") + MethodHandle getPrivateInstanceFieldInBoundary) + throws Throwable { + int result = (int) getPrivateInstanceFieldInBoundary.invoke(mateInstance); + assertThat(result).isEqualTo(80); + } + + @Test + public void getPrivateStaticArrayFieldElement( + @RuntimeMethodHandle( + className = "FieldNest", + memberName = "getPrivateStaticArrayFieldElement") + MethodHandle getPrivateStaticArrayFieldElement) + throws Throwable { + long[] arrayFieldInitialValue = {100L, 200L, 300L}; + + Field field = mate.getDeclaredField("privateStaticArrayField"); + field.setAccessible(true); + field.set(null, arrayFieldInitialValue); + + long result = (long) getPrivateStaticArrayFieldElement.invoke(1); // 1 is for index. + assertThat(result).isEqualTo(200L); + } + + @Test + public void setPrivateStaticArrayFieldElement( + @RuntimeMethodHandle( + className = "FieldNest", + memberName = "setPrivateStaticArrayFieldElement") + MethodHandle setPrivateStaticArrayFieldElement) + throws Throwable { + long[] arrayFieldInitialValue = {200L, 300L, 400L}; + long overriddenValue = 3000L; + + Field field = mate.getDeclaredField("privateStaticArrayField"); + field.setAccessible(true); + field.set(null, arrayFieldInitialValue); + + long result = (long) setPrivateStaticArrayFieldElement.invoke(1, overriddenValue); + assertThat(result).isEqualTo(overriddenValue); + + long[] actual = (long[]) field.get(null); + assertThat(actual).asList().containsExactly(200L, 3000L, 400L).inOrder(); + } + + @Test + public void getPrivateInstanceArrayFieldElement( + @RuntimeMethodHandle( + className = "FieldNest", + memberName = "getPrivateInstanceArrayFieldElement") + MethodHandle getPrivateInstanceArrayFieldElement) + throws Throwable { + int[] arrayFieldInitialValue = {300, 400, 500}; + + Field field = mate.getDeclaredField("privateInstanceArrayField"); + field.setAccessible(true); + field.set(mateInstance, arrayFieldInitialValue); + + int result = (int) getPrivateInstanceArrayFieldElement.invoke(mateInstance, 1); + assertThat(result).isEqualTo(400); + } + + @Test + public void setPrivateInstanceArrayFieldElement( + @RuntimeMethodHandle( + className = "FieldNest", + memberName = "setPrivateInstanceArrayFieldElement") + MethodHandle setPrivateInstanceArrayFieldElement) + throws Throwable { + int[] arrayFieldInitialValue = {400, 500, 600}; + int overriddenValue = 5000; + + Field field = mate.getDeclaredField("privateInstanceArrayField"); + field.setAccessible(true); + field.set(mateInstance, arrayFieldInitialValue); + + long result = + (long) setPrivateInstanceArrayFieldElement.invoke(mateInstance, 1, overriddenValue); + + assertThat(result).isEqualTo(overriddenValue); + + int[] actual = (int[]) field.get(mateInstance); + assertThat(actual).asList().containsExactly(400, 5000, 600).inOrder(); + } + + @Test + public void compoundSetPrivateStaticField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "compoundSetPrivateStaticField") + MethodHandle compoundSetPrivateStaticField) + throws Throwable { + long fieldInitialValue = 100; + long fieldValueDelta = 20; + + Field field = mate.getDeclaredField("privateStaticField"); + field.setAccessible(true); + field.set(null, fieldInitialValue); + + long result = (long) compoundSetPrivateStaticField.invoke(fieldValueDelta); + assertThat(result).isEqualTo(fieldInitialValue + fieldValueDelta); + + assertThat(field.get(null)).isEqualTo(fieldInitialValue + fieldValueDelta); + } + + @Test + public void preIncrementPrivateStaticField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "preIncrementPrivateStaticField") + MethodHandle preIncrementPrivateStaticField) + throws Throwable { + long fieldInitialValue = 200; + + Field field = mate.getDeclaredField("privateStaticField"); + field.setAccessible(true); + field.set(null, fieldInitialValue); + + long result = (long) preIncrementPrivateStaticField.invoke(); + assertThat(result).isEqualTo(fieldInitialValue + 1); + + assertThat(field.get(null)).isEqualTo(fieldInitialValue + 1); + } + + @Test + public void postIncrementPrivateStaticField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "postIncrementPrivateStaticField") + MethodHandle postIncrementPrivateStaticField) + throws Throwable { + long fieldInitialValue = 300; + + Field field = mate.getDeclaredField("privateStaticField"); + field.setAccessible(true); + field.set(null, fieldInitialValue); + + long result = (long) postIncrementPrivateStaticField.invoke(); + assertThat(result).isEqualTo(fieldInitialValue); + + assertThat(field.get(null)).isEqualTo(fieldInitialValue + 1); + } + + @Test + public void compoundSetPrivateInstanceField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "compoundSetPrivateInstanceField") + MethodHandle compoundSetPrivateInstanceField) + throws Throwable { + int fieldInitialValue = 400; + int fieldDelta = 10; + + Field field = mate.getDeclaredField("privateInstanceField"); + field.setAccessible(true); + field.set(mateInstance, fieldInitialValue); + + int result = (int) compoundSetPrivateInstanceField.invoke(mateInstance, fieldDelta); + assertThat(result).isEqualTo(fieldInitialValue + fieldDelta); + + assertThat(field.get(mateInstance)).isEqualTo(fieldInitialValue + fieldDelta); + } + + @Test + public void preIncrementPrivateInstanceField( + @RuntimeMethodHandle(className = "FieldNest", memberName = "preIncrementPrivateInstanceField") + MethodHandle preIncrementPrivateInstanceField) + throws Throwable { + int fieldInitialValue = 500; + + Field field = mate.getDeclaredField("privateInstanceField"); + field.setAccessible(true); + field.set(mateInstance, fieldInitialValue); + + int result = (int) preIncrementPrivateInstanceField.invoke(mateInstance); + assertThat(result).isEqualTo(fieldInitialValue + 1); + + assertThat(field.get(mateInstance)).isEqualTo(fieldInitialValue + 1); + } + + @Test + public void postIncrementPrivateInstanceField( + @RuntimeMethodHandle( + className = "FieldNest", + memberName = "postIncrementPrivateInstanceField") + MethodHandle postIncrementPrivateInstanceField) + throws Throwable { + int fieldInitialValue = 600; + + Field field = mate.getDeclaredField("privateInstanceField"); + field.setAccessible(true); + field.set(mateInstance, fieldInitialValue); + + int result = (int) postIncrementPrivateInstanceField.invoke(mateInstance); + assertThat(result).isEqualTo(fieldInitialValue); + + assertThat(field.get(mateInstance)).isEqualTo(fieldInitialValue + 1); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringInterfaceMethodAccessTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringInterfaceMethodAccessTest.java new file mode 100644 index 00000000000000..17467659fdb21f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringInterfaceMethodAccessTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2019 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.nest; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.DynamicClassLiteral; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.inject.Inject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.tree.ClassNode; + +/** Tests for accessing private interface methods from another class within a nest. */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public final class NestDesugaringInterfaceMethodAccessTest { + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, MethodHandles.lookup()) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .setWorkingJavaPackage( + "com.google.devtools.build.android.desugar.nest.testsrc.simpleunit.interfacemethod") + .addCommandOptions("desugar_nest_based_private_access", "true") + .build(); + + @Inject + @DynamicClassLiteral(value = "InterfaceNest$InterfaceMate") + private Class mate; + + private Object mateInstance; + + @Inject + @DynamicClassLiteral(value = "InterfaceNest$ConcreteMate") + private Class concreteMate; + + @Before + public void loadClassesInNest() throws Exception { + mateInstance = concreteMate.getDeclaredConstructor().newInstance(); + } + + @Test + public void inputClassFileMajorVersions( + @AsmNode(className = "InterfaceNest", round = 0) ClassNode beforeDesugarClassNode, + @AsmNode(className = "InterfaceNest", round = 1) ClassNode afterDesugarClassNode) { + assertThat(beforeDesugarClassNode.version).isEqualTo(JdkVersion.V11); + assertThat(afterDesugarClassNode.version).isEqualTo(JdkVersion.V1_7); + } + + @Test + public void interfaceDeclaredMethods() { + List interfaceMethodNames = + Arrays.stream(mate.getDeclaredMethods()).map(Method::getName).collect(Collectors.toList()); + assertThat(interfaceMethodNames) + .containsExactly("invokeInstanceMethodInClassBoundary", "mateValues"); + } + + @Test + public void invokePrivateStaticMethod( + @RuntimeMethodHandle(className = "InterfaceNest", memberName = "invokePrivateStaticMethod") + MethodHandle invokePrivateStaticMethod) + throws Throwable { + long result = (long) invokePrivateStaticMethod.invoke(10L, 20); + assertThat(result).isEqualTo(30L); + } + + @Test + public void invokePrivateInstanceMethod( + @RuntimeMethodHandle(className = "InterfaceNest", memberName = "invokePrivateInstanceMethod") + MethodHandle invokePrivateInstanceMethod) + throws Throwable { + long result = (long) invokePrivateInstanceMethod.invoke(mateInstance, 20L, 30); + assertThat(result).isEqualTo(50L); + } + + @Test + public void invokeInClassBoundaryStaticMethod( + @RuntimeMethodHandle( + className = "InterfaceNest", + memberName = "invokeInClassBoundaryStaticMethod") + MethodHandle invokeInClassBoundaryStaticMethod) + throws Throwable { + long result = (long) invokeInClassBoundaryStaticMethod.invoke(2L); + assertThat(result).isEqualTo(3L); // 0 + 1 + 2 + } + + @Test + public void invokeInClassBoundaryInstanceMethod( + @RuntimeMethodHandle( + className = "InterfaceNest", + memberName = "invokeInClassBoundaryInstanceMethod") + MethodHandle invokeInClassBoundaryInstanceMethod) + throws Throwable { + long result = (long) invokeInClassBoundaryInstanceMethod.invoke(mateInstance, 3L); + assertThat(result).isEqualTo(6L); // 0 + 1 + 2 + 3 + } + + @Test + public void invokePrivateStaticMethodWithLambda( + @RuntimeMethodHandle( + className = "InterfaceNest", + memberName = "invokePrivateStaticMethodWithLambda") + MethodHandle invokePrivateStaticMethodWithLambda) + throws Throwable { + long result = (long) invokePrivateStaticMethodWithLambda.invoke(2L, 3L, 4L, 5L); + assertThat(result).isEqualTo(17L); + } + + @Test + public void invokePrivateInstanceMethodWithLambda( + @RuntimeMethodHandle( + className = "InterfaceNest", + memberName = "invokePrivateInstanceMethodWithLambda") + MethodHandle invokePrivateInstanceMethodWithLambda) + throws Throwable { + long result = + (long) + invokePrivateInstanceMethodWithLambda.invoke( + mateInstance, ImmutableList.of(2L, 3L), 4L, 5L); + assertThat(result).isEqualTo(64850L); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringMethodAccessTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringMethodAccessTest.java new file mode 100644 index 00000000000000..889a3dd6bb84e8 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDesugaringMethodAccessTest.java @@ -0,0 +1,240 @@ +/* + * Copyright 2019 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.nest; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.Iterables; +import com.google.devtools.build.android.desugar.langmodel.MemberUseKind; +import com.google.devtools.build.android.desugar.langmodel.MethodInvocationSite; +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.DesugarTestHelpers; +import com.google.devtools.build.android.desugar.testing.junit.DynamicClassLiteral; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle.MemberUseContext; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.inject.Inject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +/** Tests for accessing private methods from another class within a nest. */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public final class NestDesugaringMethodAccessTest { + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, MethodHandles.lookup()) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .setWorkingJavaPackage( + "com.google.devtools.build.android.desugar.nest.testsrc.simpleunit.method") + .addCommandOptions("desugar_nest_based_private_access", "true") + .build(); + + @Inject + @DynamicClassLiteral(value = "MethodNest$MethodOwnerMate") + private Class mate; + + @Inject + @DynamicClassLiteral("MethodNest$SubMate") + private Class subClassMate; + + @Inject + @DynamicClassLiteral("MethodNest") + private Class invoker; + + private Object mateInstance; + private Object invokerInstance; + + @Before + public void loadClassesInNest() throws Exception { + mateInstance = mate.getConstructor().newInstance(); + invokerInstance = invoker.getDeclaredConstructor().newInstance(); + } + + @Test + public void inputClassFileMajorVersions( + @AsmNode(className = "MethodNest", round = 0) ClassNode beforeDesugarClassNode, + @AsmNode(className = "MethodNest", round = 1) ClassNode afterDesugarClassNode) { + assertThat(beforeDesugarClassNode.version).isEqualTo(JdkVersion.V11); + assertThat(afterDesugarClassNode.version).isEqualTo(JdkVersion.V1_7); + } + + @Test + public void methodBridgeGeneration() throws Exception { + List bridgeMethodNames = + Arrays.stream(mate.getDeclaredMethods()) + .map(Method::getName) + .filter(name -> !name.startsWith("$jacoco")) + .collect(Collectors.toList()); + assertThat(bridgeMethodNames) + .containsExactly( + "staticMethod", + "instanceMethod", + "privateStaticMethod", + "privateInstanceMethod", + "inClassBoundStaticMethod", + "inClassBoundInstanceMethod", + "privateStaticMethod$bridge", + "privateInstanceMethod$bridge"); + } + + @Test + public void invokePrivateStaticMethod_staticInitializer( + @RuntimeMethodHandle( + className = "MethodNest", + memberName = "populatedFromInvokePrivateStaticMethod", + usage = MemberUseContext.FIELD_GETTER) + MethodHandle populatedFromInvokePrivateStaticMethod) + throws Throwable { + long result = (long) populatedFromInvokePrivateStaticMethod.invoke(); + assertThat(result).isEqualTo(385L); // 128L + 256 + 1 + } + + @Test + public void invokePrivateInstanceMethod_instanceInitializer( + @RuntimeMethodHandle( + className = "MethodNest", + memberName = "populatedFromInvokePrivateInstanceMethod", + usage = MemberUseContext.FIELD_GETTER) + MethodHandle populatedFromInvokePrivateInstanceMethod) + throws Throwable { + long result = (long) populatedFromInvokePrivateInstanceMethod.invoke(invokerInstance); + assertThat(result).isEqualTo(768L); // 128L + 256 + 1 + } + + @Test + public void invokePrivateStaticMethod( + @RuntimeMethodHandle(className = "MethodNest", memberName = "invokePrivateStaticMethod") + MethodHandle invokePrivateStaticMethod) + throws Throwable { + long result = (long) invokePrivateStaticMethod.invokeExact((long) 1L, (int) 2); + assertThat(result).isEqualTo(1L + 2); + } + + @Test + public void invokePrivateInstanceMethod( + @RuntimeMethodHandle(className = "MethodNest", memberName = "invokePrivateInstanceMethod") + MethodHandle invokePrivateInstanceMethod) + throws Throwable { + long result = (long) invokePrivateInstanceMethod.invoke(mateInstance, 2L, 3); + assertThat(result).isEqualTo(2L + 3); + } + + @Test + public void invokeStaticMethod( + @RuntimeMethodHandle(className = "MethodNest", memberName = "invokeStaticMethod") + MethodHandle invokeStaticMethod) + throws Throwable { + long x = 3L; + int y = 4; + + long result = (long) invokeStaticMethod.invoke(x, y); + assertThat(result).isEqualTo(x + y); + } + + @Test + public void invokeInstanceMethod( + @RuntimeMethodHandle(className = "MethodNest", memberName = "invokeInstanceMethod") + MethodHandle invokeInstanceMethod) + throws Throwable { + long x = 4L; + int y = 5; + + long result = (long) invokeInstanceMethod.invoke(mateInstance, x, y); + assertThat(result).isEqualTo(x + y); + } + + @Test + public void invokeSuperAccessPrivateInstanceMethod( + @RuntimeMethodHandle( + className = "MethodNest", + memberName = "invokeSuperAccessPrivateInstanceMethod") + MethodHandle invokeSuperAccessPrivateInstanceMethod) + throws Throwable { + assertThat( + invokeSuperAccessPrivateInstanceMethod.invoke( + subClassMate.getConstructor().newInstance(), 7L, 8)) + .isEqualTo(16L); // 15 + 1 + } + + @Test + public void invokeCastAccessPrivateInstanceMethod( + @RuntimeMethodHandle( + className = "MethodNest", + memberName = "invokeCastAccessPrivateInstanceMethod") + MethodHandle invokeCastAccessPrivateInstanceMethod) + throws Throwable { + long result = + (long) + invokeCastAccessPrivateInstanceMethod.invoke( + subClassMate.getConstructor().newInstance(), 9L, 10); + assertThat(result).isEqualTo(21L); // 19 + 2 + } + + @Test + public void nonNestInvocationInstructions( + @AsmNode(className = "NonNest", round = 0) ClassNode before, + @AsmNode(className = "NonNest", round = 1) ClassNode after) { + assertThat(before.version).isEqualTo(JdkVersion.V11); + assertThat(after.version).isEqualTo(JdkVersion.V1_7); + } + + @Test + public void invokeVirtualOnPrivateMethod_beforeDesugaring( + @AsmNode(className = "NonNest", memberName = "invokeTwoSum", round = 0) + MethodNode invokeTwoSum) { + MethodInvocationSite twoSumInvocation = + Iterables.getOnlyElement( + DesugarTestHelpers.findMethodInvocationSites(invokeTwoSum, "NonNest", "twoSum", ".*")); + assertThat(twoSumInvocation.invocationKind()).isEqualTo(MemberUseKind.INVOKEVIRTUAL); + } + + @Test + public void invokeSpecialOnPrivateMethod_afterDesugaring( + @AsmNode(className = "NonNest", memberName = "invokeTwoSum", round = 1) + MethodNode invokeTwoSum) { + MethodInvocationSite twoSumInvocation = + Iterables.getOnlyElement( + DesugarTestHelpers.findMethodInvocationSites(invokeTwoSum, "NonNest", "twoSum", ".*")); + assertThat(twoSumInvocation.invocationKind()).isEqualTo(MemberUseKind.INVOKESPECIAL); + } + + @Test + public void pnvokeSpecialOnPrivateMethod_afterDesugaringExecution( + @RuntimeMethodHandle(className = "NonNest", memberName = "invokeTwoSum", round = 1) + MethodHandle invokeTwoSum) + throws Throwable { + long result = (long) invokeTwoSum.invoke(1000L, 2L, 3L); + assertThat(result).isEqualTo(1005L); + } + +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/NestDigestTest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDigestTest.java new file mode 100644 index 00000000000000..0f951328f79ce1 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/NestDigestTest.java @@ -0,0 +1,168 @@ +// Copyright 2019 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.nest; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord.ClassAttributeRecordBuilder; +import com.google.devtools.build.android.desugar.langmodel.ClassAttributes; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord; +import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord.ClassMemberRecordBuilder; +import com.google.devtools.build.android.desugar.langmodel.ClassName; +import com.google.devtools.build.android.desugar.langmodel.MethodKey; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.Opcodes; + +/** The tests for {@link NestDigest}. */ +@RunWith(JUnit4.class) +public final class NestDigestTest { + + private final ClassMemberRecordBuilder classMemberRecord = ClassMemberRecord.builder(); + private final ClassAttributeRecordBuilder classAttributeRecord = ClassAttributeRecord.builder(); + + @Test + public void prepareCompanionClassWriters_noCompanionClassesGenerated() { + classMemberRecord.logMemberDecl( + MethodKey.create(ClassName.create("package/path/OwnerClass"), "method", "(II)I"), + /* ownerAccess= */ Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, + /* memberDeclAccess= */ Opcodes.ACC_PRIVATE); + + NestDigest nestDigest = + NestDigest.builder() + .setClassMemberRecord(classMemberRecord.build()) + .setClassAttributeRecord(classAttributeRecord.build()) + .build(); + + assertThat(nestDigest.getAllCompanionClassNames()).isEmpty(); + } + + @Test + public void prepareCompanionClassWriters_companionClassesGenerated() { + MethodKey constructor = + MethodKey.create(ClassName.create("package/path/OwnerClass"), "", "()V"); + classMemberRecord.logMemberDecl( + constructor, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE); + classMemberRecord.logMemberUse(constructor, Opcodes.INVOKESPECIAL); + classAttributeRecord.addClassAttributes( + ClassAttributes.builder() + .setClassBinaryName(ClassName.create("package/path/OwnerClass$NestedClass")) + .setNestHost(ClassName.create("package/path/OwnerClass")) + .build()); + classAttributeRecord.addClassAttributes( + ClassAttributes.builder() + .setClassBinaryName(ClassName.create("package/path/OwnerClass")) + .addNestMember(ClassName.create("package/path/OwnerClass$NestedClass")) + .build()); + + NestDigest nestDigest = + NestDigest.builder() + .setClassMemberRecord(classMemberRecord.build()) + .setClassAttributeRecord(classAttributeRecord.build()) + .build(); + + assertThat(nestDigest.getAllCompanionClassNames()) + .containsExactly("package/path/OwnerClass$NestCC"); + } + + @Test + public void preparCompanionClassWriters_multipleCompanionClassesGenerated() { + classMemberRecord.logMemberDecl( + MethodKey.create(ClassName.create("package/path/OwnerClassA"), "", "()V"), + Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, + Opcodes.ACC_PRIVATE); + classMemberRecord.logMemberDecl( + MethodKey.create(ClassName.create("package/path/OwnerClassA"), "method", "(II)I"), + Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, + Opcodes.ACC_PRIVATE); + classMemberRecord.logMemberDecl( + MethodKey.create(ClassName.create("package/path/OwnerClassB"), "", "()V"), + Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, + Opcodes.ACC_PRIVATE); + + classMemberRecord.logMemberUse( + MethodKey.create(ClassName.create("package/path/OwnerClassA"), "", "()V"), + Opcodes.INVOKESPECIAL); + classMemberRecord.logMemberUse( + MethodKey.create(ClassName.create("package/path/OwnerClassA"), "method", "(II)I"), + Opcodes.INVOKESPECIAL); + classMemberRecord.logMemberUse( + MethodKey.create(ClassName.create("package/path/OwnerClassB"), "", "()V"), + Opcodes.INVOKESPECIAL); + + classAttributeRecord.addClassAttributes( + ClassAttributes.builder() + .setClassBinaryName(ClassName.create("package/path/OwnerClassA$NestedClass")) + .setNestHost(ClassName.create("package/path/OwnerClassA")) + .build()); + classAttributeRecord.addClassAttributes( + ClassAttributes.builder() + .setClassBinaryName(ClassName.create("package/path/OwnerClassB$NestedClass")) + .setNestHost(ClassName.create("package/path/OwnerClassB")) + .build()); + + classAttributeRecord.addClassAttributes( + ClassAttributes.builder() + .setClassBinaryName(ClassName.create("package/path/OwnerClassA")) + .addNestMember(ClassName.create("package/path/OwnerClassA$NestedClass")) + .build()); + classAttributeRecord.addClassAttributes( + ClassAttributes.builder() + .setClassBinaryName(ClassName.create("package/path/OwnerClassB")) + .addNestMember(ClassName.create("package/path/OwnerClassB$NestedClass")) + .build()); + + NestDigest nestDigest = + NestDigest.builder() + .setClassMemberRecord(classMemberRecord.build()) + .setClassAttributeRecord(classAttributeRecord.build()) + .build(); + + assertThat(nestDigest.getAllCompanionClassNames()) + .containsExactly("package/path/OwnerClassA$NestCC", "package/path/OwnerClassB$NestCC"); + } + + @Test + public void prepareCompanionClassWriters_classNameWithDollarSign() { + MethodKey constructor = + MethodKey.create(ClassName.create("package/path/$Owner$Class$"), "", "()V"); + + classMemberRecord.logMemberDecl( + constructor, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, Opcodes.ACC_PRIVATE); + classMemberRecord.logMemberUse(constructor, Opcodes.INVOKESPECIAL); + + classAttributeRecord.addClassAttributes( + ClassAttributes.builder() + .setClassBinaryName(ClassName.create(constructor.ownerName() + "$NestClass")) + .setNestHost(ClassName.create(constructor.ownerName())) + .build()); + classAttributeRecord.addClassAttributes( + ClassAttributes.builder() + .setClassBinaryName(ClassName.create(constructor.ownerName())) + .addNestMember(ClassName.create(constructor.ownerName() + "$NestClass")) + .build()); + + NestDigest nestDigest = + NestDigest.builder() + .setClassMemberRecord(classMemberRecord.build()) + .setClassAttributeRecord(classAttributeRecord.build()) + .build(); + + assertThat(nestDigest.getAllCompanionClassNames()) + .containsExactly("package/path/$Owner$Class$$NestCC"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Alpha.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Alpha.java new file mode 100644 index 00000000000000..ae45c733e4ac64 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Alpha.java @@ -0,0 +1,39 @@ +// Copyright 2019 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.nest.testsrc.complexcase; + +/** For testing private interface methods desugaring. */ +public interface Alpha { + + long VAL = 1000L; + + static long publicStaticMethod(Alpha alpha, Bravo bravo, long x, int y) { + return Alpha.VAL + bravo.abstractMethod(x, y) + privateStaticMethod(alpha, bravo, x, y); + } + + static long privateStaticMethod(Alpha alpha, Bravo bravo, long x, int y) { + return Bravo.VAL + alpha.abstractMethod(x, y) + Bravo.publicStaticMethod(alpha, bravo, x, y); + } + + default long defaultMethod(Bravo bravo, long x, int y) { + return Alpha.VAL + bravo.abstractMethod(x, y) + privateInstanceMethod(bravo, x, y); + } + + private long privateInstanceMethod(Bravo bravo, long x, int y) { + return Bravo.VAL + abstractMethod(x, y) + bravo.defaultMethod(this, x, y); + } + + long abstractMethod(long x, int y); +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/BUILD b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/BUILD new file mode 100644 index 00000000000000..9bfe7e05b9f8cf --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/BUILD @@ -0,0 +1,17 @@ +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "complexcase", + srcs = glob(["*.java"]), +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Bravo.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Bravo.java new file mode 100644 index 00000000000000..bfd14e2cede8a0 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Bravo.java @@ -0,0 +1,51 @@ +// Copyright 2019 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.nest.testsrc.complexcase; + +/** For testing private interface methods desugaring. */ +public interface Bravo { + + long VAL = 1_000_000L; + + static long publicStaticMethod(Alpha alpha, Bravo bravo, long x, int y) { + return Alpha.VAL + bravo.abstractMethod(x, y) + privateStaticMethod(alpha, bravo, x, y); + } + + static long privateStaticMethod(Alpha alpha, Bravo bravo, long x, int y) { + return Alpha.VAL + bravo.abstractMethod(x, y) + bravo.privateInstanceMethod(alpha, x, y); + } + + private long privateInstanceMethod(Alpha alpha, long x, int y) { + return Bravo.VAL + alpha.abstractMethod(x, y) + alpha.defaultMethod(this, x, y); + } + + default long defaultMethod(Alpha alpha, long x, int y) { + return Bravo.VAL + alpha.abstractMethod(x, y) + abstractMethod(x, y); + } + + private long crossMatePrivateInstanceMethod() { + return 123L; + } + + long abstractMethod(long x, int y); + + /** For testing private interface methods desugaring. */ + interface Charlie extends Bravo { + default long invokeCrossMatePrivateInstanceMethod() { + // Emits invokespecial instruction. + return Bravo.super.crossMatePrivateInstanceMethod(); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Phloem.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Phloem.java new file mode 100644 index 00000000000000..9a269fa054b3ea --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Phloem.java @@ -0,0 +1,18 @@ +// Copyright 2019 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.nest.testsrc.complexcase; + +/** A test class for desugaring operations. */ +public class Phloem {} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Xylem.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Xylem.java new file mode 100644 index 00000000000000..8c82018e3d2af6 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/complexcase/Xylem.java @@ -0,0 +1,72 @@ +// Copyright 2019 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.nest.testsrc.complexcase; + +import com.google.devtools.build.android.desugar.nest.testsrc.complexcase.Bravo.Charlie; + +/** Test class as source data. */ +@SuppressWarnings({"PrivateConstructorForUtilityClass", "FieldCanBeFinal"}) // For testing. +public class Xylem { + static class ConcreteAlpha implements Alpha { + private ConcreteAlpha() {} + + private static long privateStaticField = ConcreteBravo.privateStaticMethod(); + private int privateInstanceField = 2; + + @Override + public long abstractMethod(long x, int y) { + return x + y + 1_000_000_000L; + } + } + + static class ConcreteBravo extends ConcreteAlpha implements Bravo { + private ConcreteBravo() {} + + private static long privateStaticMethod() { + return 1L; + } + + private int privateInstanceMethod() { + return super.privateInstanceField; + } + + @Override + public long abstractMethod(long x, int y) { + return x + y + 2_000_000_000L; + } + } + + private static class XylemInvoker { + private static long execute(long x, int y) { + ConcreteBravo bravo = new ConcreteBravo(); + long localSum = x + y; + Charlie charlie = (a, b) -> a + b + localSum; + return ConcreteAlpha.privateStaticField + + bravo.privateInstanceMethod() + + Alpha.publicStaticMethod(new ConcreteAlpha(), bravo, x, y) + + charlie.invokeCrossMatePrivateInstanceMethod(); + } + } + + public static long execute(long x, int y) { + return XylemInvoker.execute(x, y); + } + + public static void main(String[] args) { + int x = Integer.parseInt(args[0]); + int y = Integer.parseInt(args[1]); + System.out.println(execute(x, y)); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer/AnalyzedTarget.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer/AnalyzedTarget.java new file mode 100644 index 00000000000000..9462db3e4d478a --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer/AnalyzedTarget.java @@ -0,0 +1,29 @@ +// Copyright 2019 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.nest.testsrc.nestanalyzer; + +@SuppressWarnings("PrivateConstructorForUtilityClass") // As testing source. +class AnalyzedTarget { + + private static class EnclosedTargetAlpha { + private EnclosedTargetAlpha() {} + } + + static class EnclosedTargetBravo { + public static void main(String[] args) { + System.out.println(new EnclosedTargetAlpha()); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer/BUILD b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer/BUILD new file mode 100644 index 00000000000000..266d59e493a431 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/nestanalyzer/BUILD @@ -0,0 +1,17 @@ +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "analyzed_target", + srcs = glob(["*.java"]), +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat/BUILD b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat/BUILD new file mode 100644 index 00000000000000..5376b5b2bf9fbe --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat/BUILD @@ -0,0 +1,17 @@ +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "classfileformat", + srcs = glob(["*.java"]), +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat/NestOuterInterfaceA.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat/NestOuterInterfaceA.java new file mode 100644 index 00000000000000..6c651692b39f4a --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/classfileformat/NestOuterInterfaceA.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020 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.nest.testsrc.simpleunit.classfileformat; + +/** A nest for testing the attributes in class file format. */ +@SuppressWarnings("InterfaceWithOnlyStatics") // Contrived class for nest-access based testing. +public interface NestOuterInterfaceA { + + /** A nested class. */ + class NestedClassB {} + + /** A nested interface. */ + interface NestedInterfaceC {} + + /** A nested annotation. */ + @interface NestedAnnotationD {} + + /** A nested enum. */ + enum NestedEnumE {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/BUILD b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/BUILD new file mode 100644 index 00000000000000..b61987f6b21e99 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/BUILD @@ -0,0 +1,17 @@ +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "constructor", + srcs = glob(["*.java"]), +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/ConstructorNest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/ConstructorNest.java new file mode 100644 index 00000000000000..c2c2c47b25632a --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/ConstructorNest.java @@ -0,0 +1,46 @@ +// Copyright 2019 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.nest.testsrc.simpleunit.constructor; + +/** A nest for testing private constructor desugaring. */ +public class ConstructorNest { + + /** A nest member class that encloses private constructors with cross-mate invocations. */ + public static class ConstructorServiceMate { + private final long x; + private final int y; + + private ConstructorServiceMate() throws Exception { + this(10L, 20); + } + + private ConstructorServiceMate(long x, int y) throws Exception { + this.x = x; + this.y = y; + } + + long getSum() { + return x + y; + } + } + + public static long createFromZeroArgConstructor() throws Exception { + return new ConstructorServiceMate().getSum(); + } + + public static long createFromMultiArgConstructor(long x, int y) throws Exception { + return new ConstructorServiceMate(x, y).getSum(); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/DollarSignNamedNest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/DollarSignNamedNest.java new file mode 100644 index 00000000000000..9a5ff07f81634e --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/constructor/DollarSignNamedNest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 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.nest.testsrc.simpleunit.constructor; + +/** The entry-point point for testing to desugar classes with '$' in class names. */ +public class DollarSignNamedNest { + public static long execute(long initialValue) { + return $Dollar$Sign$Named$Nest$.execute(initialValue); + } +} + +class $Dollar$Sign$Named$Nest$ { + + private final long value; + + static long execute(long value) { + return new $Dollar$Sign$Named$Nest$(value).toMember().toHost().value; + } + + private $Dollar$Sign$Named$Nest$(long value) { + this.value = value + 1; + } + + $Dollar$Sign$Named$Member$ toMember() { + return new $Dollar$Sign$Named$Member$(this); + } + + static class $Dollar$Sign$Named$Member$ { + + private final long value; + + private $Dollar$Sign$Named$Member$($Dollar$Sign$Named$Nest$ host) { + value = host.value + 2; + } + + $Dollar$Sign$Named$Nest$ toHost() { + return new $Dollar$Sign$Named$Nest$(value); + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/core/javadesugar/testing/BUILD b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/core/javadesugar/testing/BUILD new file mode 100644 index 00000000000000..300fa54d7b7881 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/core/javadesugar/testing/BUILD @@ -0,0 +1,17 @@ +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "testing", + srcs = glob(["*.java"]), +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/core/javadesugar/testing/TestCoreType.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/core/javadesugar/testing/TestCoreType.java new file mode 100644 index 00000000000000..6f42dd1b4f6d66 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/core/javadesugar/testing/TestCoreType.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 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 javadesugar.testing; + +/** + * A fake core library class for testing core type desugaring. Related flags include, + * + *

      --core_library, --desugar_supported_core_libs, --rewrite_core_library_prefix + */ +public class TestCoreType { + + /** Invocation entry point for testing to invoke private static methods in anther mate. */ + public static long twoSum(long x, long y) { + return MateA.twoSum(x, y); + } + + /** Invocation entry point for testing to invoke private instance methods in anther mate. */ + public static long twoSumWithBase(long base, long x, long y) { + return new MateA(base).twoSumWithBase(x, y); + } + + private TestCoreType() {} + + private static class MateA { + + private final long base; + + private MateA(long base) { + this.base = base; + } + + private static long twoSum(long x, long y) { + return x + y; + } + + private long twoSumWithBase(long x, long y) { + return base + x + y; + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field/BUILD b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field/BUILD new file mode 100644 index 00000000000000..4ccfdf6db8b549 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field/BUILD @@ -0,0 +1,17 @@ +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "field", + srcs = glob(["*.java"]), +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field/FieldNest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field/FieldNest.java new file mode 100644 index 00000000000000..012aaac0a4791e --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/field/FieldNest.java @@ -0,0 +1,139 @@ +// Copyright 2019 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.nest.testsrc.simpleunit.field; + +/** A nest for testing private field desugaring. */ +public class FieldNest { + + /** A nest member that encloses private fields with cross-mate reads and writes. */ + public static class FieldOwnerMate { + + static long staticField = 10L; + + int instanceField = 20; + + private static long privateStaticField = 30L; + + private int privateInstanceField = 40; + + private long privateInstanceWideField = 45L; + + private static long privateStaticFieldReadOnly = 50L; + + private int privateInstanceFieldReadOnly = 60; + + private static long privateStaticFieldInBoundary = 70L; + + private int privateInstanceFieldInBoundary = 80; + + private static long[] privateStaticArrayField = {90L, 100L}; + + private int[] privateInstanceArrayField = {110, 120}; + + public static long getPrivateStaticFieldInBoundary() { + return privateStaticFieldInBoundary; + } + + public int getPrivateInstanceFieldInBoundary() { + return privateInstanceFieldInBoundary; + } + } + + public static synchronized long getStaticField() { + return FieldOwnerMate.staticField; + } + + public static synchronized int getInstanceField(FieldOwnerMate mate) { + return mate.instanceField; + } + + public static synchronized long getPrivateStaticField() { + return FieldOwnerMate.privateStaticField; + } + + public static synchronized long setPrivateStaticField(long x) { + return FieldOwnerMate.privateStaticField = x; + } + + public static synchronized int getPrivateInstanceField(FieldOwnerMate mate) { + return mate.privateInstanceField; + } + + public static synchronized int setPrivateInstanceField(FieldOwnerMate mate, int x) { + return mate.privateInstanceField = x; + } + + public static synchronized long setPrivateInstanceWideField(FieldOwnerMate mate, long x) { + return mate.privateInstanceWideField = x; + } + + public static synchronized long getPrivateStaticArrayFieldElement(int index) { + return FieldOwnerMate.privateStaticArrayField[index]; + } + + public static synchronized long setPrivateStaticArrayFieldElement(int index, long value) { + return FieldOwnerMate.privateStaticArrayField[index] = value; + } + + public static synchronized int getPrivateInstanceArrayFieldElement( + FieldOwnerMate mate, int index) { + return mate.privateInstanceArrayField[index]; + } + + public static synchronized int setPrivateInstanceArrayFieldElement( + FieldOwnerMate mate, int index, int value) { + return mate.privateInstanceArrayField[index] = value; + } + + public static synchronized long getPrivateStaticFieldReadOnly() { + return FieldOwnerMate.privateStaticFieldReadOnly; + } + + public static synchronized int getPrivateInstanceFieldReadOnly(FieldOwnerMate mate) { + return mate.privateInstanceFieldReadOnly; + } + + public static synchronized long getPrivateStaticFieldInBoundary() { + return FieldOwnerMate.getPrivateStaticFieldInBoundary(); + } + + public static synchronized int getPrivateInstanceFieldInBoundary(FieldOwnerMate mate) { + return mate.getPrivateInstanceFieldInBoundary(); + } + + public static synchronized long compoundSetPrivateStaticField(long x) { + return FieldOwnerMate.privateStaticField += x; + } + + public static synchronized long preIncrementPrivateStaticField() { + return ++FieldOwnerMate.privateStaticField; + } + + public static synchronized long postIncrementPrivateStaticField() { + return FieldOwnerMate.privateStaticField++; + } + + public static synchronized int compoundSetPrivateInstanceField(FieldOwnerMate mate, int x) { + return mate.privateInstanceField += x; + } + + public static synchronized int preIncrementPrivateInstanceField(FieldOwnerMate mate) { + return ++mate.privateInstanceField; + } + + public static synchronized int postIncrementPrivateInstanceField(FieldOwnerMate mate) { + return mate.privateInstanceField++; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod/BUILD b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod/BUILD new file mode 100644 index 00000000000000..c842df06e07bf9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod/BUILD @@ -0,0 +1,17 @@ +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "interfacemethod", + srcs = glob(["*.java"]), +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod/InterfaceNest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod/InterfaceNest.java new file mode 100644 index 00000000000000..b223d25e3d00ac --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/interfacemethod/InterfaceNest.java @@ -0,0 +1,129 @@ +// Copyright 2019 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.nest.testsrc.simpleunit.interfacemethod; + +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +/** A nest for testing private interface methods desugaring. */ +public class InterfaceNest { + + /** A class that implements the interface mate. */ + public static class ConcreteMate implements InterfaceMate { + @Override + public List mateValues() { + return LongStream.range(0L, 100L).boxed().collect(Collectors.toList()); + } + } + /** A nest member that encloses private interface methods with cross-mate invocations. */ + interface SubInterfaceMateMate { + private static long privateStaticMethod(long x, int y) { + return x + y; + } + + private long privateInstanceMethod(long x, int y) { + return x + y; + } + } + + interface InterfaceMate { + + static long publicStaticMethod(long x, long y) { + return x + y; + } + + private static long privateStaticMethod(long x, int y) { + return x + y; + } + + private long privateInstanceMethod(long x, int y) { + return x + y; + } + + private static long privateStaticMethodWithLambdaEvaluation( + Function> hf, long a, long b) { + return hf.apply(a).apply(b); + } + + private static Function> privateStaticMethodWithLambdaGeneration( + long a, long b) { + return p -> (q -> a + b * q); + } + + private long privateInstanceMethodWithLambda(Collection factors, long a, long b) { + Long sum = factors.stream().map(v -> a + v).reduce(0L, Long::sum); + return mateValues().stream().mapToLong(v -> b + sum * v).sum(); + } + + List mateValues(); + + private static long inClassBoundaryStaticMethod(long x) { + return x == 0 ? 0 : x + inClassBoundaryStaticMethod(x - 1); + } + + private long inClassBoundaryInstanceMethod(long x) { + return x == 0 ? 0 : x + inClassBoundaryInstanceMethod(x - 1); + } + + static long invokeStaticMethodInClassBoundary(long x) { + return inClassBoundaryStaticMethod(x); + } + + default long invokeInstanceMethodInClassBoundary(long x) { + return inClassBoundaryInstanceMethod(x); + } + } + + public static long invokePublicStaticMethod(long x, int y) { + return InterfaceMate.publicStaticMethod(x, y); + } + + public static long invokePrivateStaticMethod(long x, int y) { + return InterfaceMate.privateStaticMethod(x, y); + } + + public static long invokePrivateInstanceMethod(InterfaceMate mate, long x, int y) { + return mate.privateInstanceMethod(x, y); + } + + public static long invokeSubMatePrivateStaticMethod(long x, int y) { + return SubInterfaceMateMate.privateStaticMethod(x, y); + } + + public static long invokeSubMatePrivateInstanceMethod(SubInterfaceMateMate mate, long x, int y) { + return mate.privateInstanceMethod(x, y); + } + + public static long invokePrivateStaticMethodWithLambda(long a0, long b0, long a1, long b1) { + return InterfaceMate.privateStaticMethodWithLambdaEvaluation( + InterfaceMate.privateStaticMethodWithLambdaGeneration(a0, b0), a1, b1); + } + + public static long invokePrivateInstanceMethodWithLambda( + InterfaceMate mate, Collection vals, long a, long b) { + return mate.privateInstanceMethodWithLambda(vals, a, b); + } + + public static long invokeInClassBoundaryStaticMethod(long x) { + return InterfaceMate.invokeStaticMethodInClassBoundary(x); + } + + public static long invokeInClassBoundaryInstanceMethod(InterfaceMate mate, long x) { + return mate.invokeInstanceMethodInClassBoundary(x); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/BUILD b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/BUILD new file mode 100644 index 00000000000000..01a403bd29adb9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/BUILD @@ -0,0 +1,17 @@ +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "method", + srcs = glob(["*.java"]), +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/MethodNest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/MethodNest.java new file mode 100644 index 00000000000000..e2e3c845379ee6 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/MethodNest.java @@ -0,0 +1,118 @@ +// Copyright 2019 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.nest.testsrc.simpleunit.method; + +/** A nest for testing private method desugaring. */ +@SuppressWarnings("MethodCanBeStatic") // Intentional for testing. +public class MethodNest { + + /** A nest member that encloses private methods with cross-mate invocations. */ + public static class MethodOwnerMate { + + static long staticMethod(long x, int y) { + return x + y; + } + + long instanceMethod(long x, int y) { + return x + y; + } + + private static long privateStaticMethod(long x, int y) { + return x + y; + } + + private long privateInstanceMethod(long x, int y) throws Exception { + return x + y; + } + + // No generation of bridge methods. + private long inClassBoundInstanceMethod(long x) { + return x == 0 ? 0 : x + inClassBoundInstanceMethod(x - 1); + } + + // No generation of bridge methods. + private static long inClassBoundStaticMethod(long x) { + return x == 0 ? 0 : x + inClassBoundStaticMethod(x - 1); + } + } + + /** A nest member that has access to cross-mate private methods through inheritance. */ + public static class SubMate extends MethodOwnerMate { + + public static long invokePrivateStaticMethod(long x, int y) { + return MethodOwnerMate.privateStaticMethod(x, y); + } + + public long superAccessPrivateInstanceMethod(long x, int y) throws Exception { + return 1 + super.privateInstanceMethod(x, y); + } + + public long castAccessPrivateInstanceMethod(long x, int y) throws Exception { + return 2 + ((MethodOwnerMate) this).privateInstanceMethod(x, y); + } + } + + public static long populatedFromInvokePrivateStaticMethod; + public long populatedFromInvokePrivateInstanceMethod; + + // For testing a static initializer block. + static { + long t = MethodOwnerMate.privateStaticMethod(128L, 256); + populatedFromInvokePrivateStaticMethod = 1 + t; + } + + // For testing a instance initializer block. + { + try { + populatedFromInvokePrivateInstanceMethod = + new MethodOwnerMate().privateInstanceMethod(256L, 512); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("StaticQualifiedUsingExpression") // Intentionally for testing. + public static long invokeStaticMethod(long x, int y) { + MethodOwnerMate methodOwnerMate = null; + return methodOwnerMate.staticMethod(x, y); + } + + public static long invokeInstanceMethod(MethodOwnerMate mateInstance, long x, int y) { + return mateInstance.instanceMethod(x, y); + } + + public static long invokePrivateStaticMethod(long x, int y) { + return MethodOwnerMate.privateStaticMethod(x, y); + } + + public static long invokePrivateInstanceMethod(MethodOwnerMate mateInstance, long x, int y) + throws Exception { + return mateInstance.privateInstanceMethod(x, y); + } + + public static long invokeSuperAccessPrivateInstanceMethod(SubMate subMate, long x, int y) + throws Exception { + return subMate.superAccessPrivateInstanceMethod(x, y); + } + + public static long invokeCastAccessPrivateInstanceMethod(SubMate subMate, long x, int y) + throws Exception { + return subMate.castAccessPrivateInstanceMethod(x, y); + } + + public static void main(String[] args) { + System.out.println("hello2"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/NonNest.java b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/NonNest.java new file mode 100644 index 00000000000000..0195690cc3b68f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/nest/testsrc/simpleunit/method/NonNest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 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.nest.testsrc.simpleunit.method; + +/** + * Source code for testing the desugaring of invokevirtual at call the site of private instance + * method within the same class. + */ +public final class NonNest { + + private final long base; + + private NonNest(long base) { + this.base = base; + } + + private long twoSum(long x, long y) { + return base + x + y; + } + + public static long invokeTwoSum(long base, long x, long y) { + return new NonNest(base) // Expected invokespecial + .twoSum(x, y); // Expected invokevirtual under javac 11. + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/scan/BUILD b/src/test/java/com/google/devtools/build/android/desugar/scan/BUILD new file mode 100644 index 00000000000000..d2203d8789a132 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/scan/BUILD @@ -0,0 +1,36 @@ +load("@rules_java//java:defs.bzl", "java_library") + +# Description: +# Tests for the Java 8 desugaring tool for Android. +package( + default_testonly = 1, +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["**"]), + visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__pkg__"], +) + +sh_test( + name = "test_keep_scanner", + srcs = ["test_keep_scanner.sh"], + args = [ + "$(location //src/tools/android/java/com/google/devtools/build/android/desugar/scan:KeepScanner)", + "$(location :testdata)", + "$(location testdata_golden.txt)", + ], + data = [ + "testdata_golden.txt", + ":testdata", + "//src/tools/android/java/com/google/devtools/build/android/desugar/scan:KeepScanner", + ], +) + +java_library( + name = "testdata", + srcs = glob(["testdata/**/*.java"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/scan/test_keep_scanner.sh b/src/test/java/com/google/devtools/build/android/desugar/scan/test_keep_scanner.sh new file mode 100755 index 00000000000000..d42859fe35c322 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/scan/test_keep_scanner.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# +# Copyright 2018 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. +set -eux + +out=$(mktemp) +"$1" --input "$2" --keep_file "${out}" --prefix java/ + +if ! diff "$3" "${out}"; then + echo "Unexpected output" + cat "${out}" + rm "${out}" + exit 1 +fi +rm "${out}" diff --git a/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java new file mode 100644 index 00000000000000..830364c06c2c84 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/CollectionReferences.java @@ -0,0 +1,64 @@ +// Copyright 2018 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.scan.testdata; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** Test data for {@code KeepScanner} with references to java.* */ +public class CollectionReferences { + + private final List dates; + + public CollectionReferences() { + dates = new ArrayList<>(7); + assert !(dates instanceof LinkedList); + } + + @SuppressWarnings("unchecked") + public void add(Date date) { + List l = (AbstractList) Collection.class.cast(dates); + l.add(date); + } + + public Date first() { + try { + return dates.get(0); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + public long min() { + long result = Long.MAX_VALUE; // compile-time constant, no ref + for (Date d : dates) { + if (d.getTime() < result) { + result = d.getTime(); + } + } + return result; + } + + public void expire(long before) { + dates.removeIf(d -> d.getTime() < before); + } + + static { + System.out.println("Hello!"); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/OverlappingCollectionReferences.java b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/OverlappingCollectionReferences.java new file mode 100644 index 00000000000000..e743a0df7a34ff --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata/OverlappingCollectionReferences.java @@ -0,0 +1,49 @@ +// Copyright 2018 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.scan.testdata; + +import java.util.ArrayList; +import java.util.Date; + +/** Supplements {@link CollectionReferences} with additional and overlapping references to java.* */ +public class OverlappingCollectionReferences { + + private final ArrayList dates; + + public OverlappingCollectionReferences() { + dates = new ArrayList<>(); + } + + public void add(Date date) { + dates.add(date); + } + + public Date first() { + try { + return dates.get(0); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + public Date max() { + long result = Long.MIN_VALUE; // compile-time constant, no ref + for (Date d : dates) { + if (d.getTime() > result) { + result = d.getTime(); + } + } + return new Date(result); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt new file mode 100644 index 00000000000000..60825763f5b1a9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/scan/testdata_golden.txt @@ -0,0 +1,62 @@ +-keep class java.io.PrintStream { + *** println(java.lang.String); +} +-keep class java.lang.AssertionError { + (); +} +-keep class java.lang.Class { + *** cast(java.lang.Object); + *** desiredAssertionStatus(); +} +-keep class java.lang.IndexOutOfBoundsException { +} +-keep class java.lang.Object { + (); +} +-keep class java.lang.String { +} +-keep class java.lang.System { + *** out; +} +-keep class java.lang.invoke.CallSite { +} +-keep class java.lang.invoke.LambdaMetafactory { + *** metafactory(java.lang.invoke.MethodHandles$Lookup, java.lang.String, java.lang.invoke.MethodType, java.lang.invoke.MethodType, java.lang.invoke.MethodHandle, java.lang.invoke.MethodType); +} +-keep class java.lang.invoke.MethodHandle { +} +-keep class java.lang.invoke.MethodHandles { +} +-keep class java.lang.invoke.MethodHandles$Lookup { +} +-keep class java.lang.invoke.MethodType { +} +-keep class java.util.AbstractList { +} +-keep class java.util.ArrayList { + (); + (int); + *** add(java.lang.Object); + *** get(int); + *** iterator(); +} +-keep class java.util.Collection { + *** removeIf(java.util.function.Predicate); +} +-keep class java.util.Date { + (long); + *** getTime(); +} +-keep class java.util.Iterator { + *** hasNext(); + *** next(); +} +-keep class java.util.LinkedList { +} +-keep class java.util.List { + *** add(java.lang.Object); + *** get(int); + *** iterator(); +} +-keep class java.util.function.Predicate { +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/stringconcat/BUILD b/src/test/java/com/google/devtools/build/android/desugar/stringconcat/BUILD new file mode 100644 index 00000000000000..6f1a9ea4c08bdc --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/stringconcat/BUILD @@ -0,0 +1,59 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_test") + +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +java_test( + name = "IndyStringConcatDesugaringlTest", + size = "medium", + srcs = ["IndyStringConcatDesugaringlTest.java"], + data = [ + ":string_concat_cases_srcs", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_srcs=$(locations :string_concat_cases_srcs)", + # Required by Desugar#verifyLambdaDumpDirectoryRegistered + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + "-Djava.lang.invoke.stringConcat=MH_INLINE_SIZED_EXACT", + ], + test_class = "com.google.devtools.build.android.desugar.stringconcat.IndyStringConcatDesugaringlTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:jsr330_inject", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_library( + name = "string_concat_cases", + srcs = [":string_concat_cases_srcs"], + javacopts = [ + "-source 11", + "-target 11", + "-XDstringConcat=indyWithConstants", # Enable OpenJDK version of String concat. + ], +) + +filegroup( + name = "string_concat_cases_srcs", + srcs = ["StringConcatTestCases.java"], +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/stringconcat/IndyStringConcatDesugaringlTest.java b/src/test/java/com/google/devtools/build/android/desugar/stringconcat/IndyStringConcatDesugaringlTest.java new file mode 100644 index 00000000000000..530a114bd9e835 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/stringconcat/IndyStringConcatDesugaringlTest.java @@ -0,0 +1,320 @@ +/* + * Copyright 2021 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.stringconcat; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.FromParameterValueSource; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import com.google.devtools.build.android.desugar.testing.junit.ParameterValueSource; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.Arrays; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Tests for accessing a series of private fields, constructors and methods from another class + * within a nest. + */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public final class IndyStringConcatDesugaringlTest { + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, MethodHandles.lookup()) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("-source 11", "-target 11", "-XDstringConcat=indyWithConstants") + .setWorkingJavaPackage("com.google.devtools.build.android.desugar.stringconcat") + .addCommandOptions("desugar_indy_string_concat", "true") + .build(); + + @Test + public void invokeDynamicInstr_presentBeforeDesugaring( + @AsmNode(className = "StringConcatTestCases", memberName = "simplePrefix", round = 0) + MethodNode simpleStrConcatBeforeDesugar) { + AbstractInsnNode[] instructions = simpleStrConcatBeforeDesugar.instructions.toArray(); + assertThat(Arrays.stream(instructions).map(AbstractInsnNode::getOpcode)) + .contains(Opcodes.INVOKEDYNAMIC); + } + + @Test + public void invokeDynamicInstr_absentAfterDesugaring( + @AsmNode(className = "StringConcatTestCases", memberName = "simplePrefix", round = 1) + MethodNode simpleStrConcatAfterDesugar) { + AbstractInsnNode[] instructions = simpleStrConcatAfterDesugar.instructions.toArray(); + assertThat(Arrays.stream(instructions).map(AbstractInsnNode::getOpcode)) + .doesNotContain(Opcodes.INVOKEDYNAMIC); + } + + @Test + @ParameterValueSource({"", "", ""}) + @ParameterValueSource({"", "b", "b"}) + @ParameterValueSource({"a", "", "a"}) + @ParameterValueSource({"a", "b", "ab"}) + @ParameterValueSource({"ab", "cd", "abcd"}) + public void twoConcat( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "twoConcat", + memberDescriptor = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") + MethodHandle twoConcat, + @FromParameterValueSource String x, + @FromParameterValueSource String y, + @FromParameterValueSource String expectedResult) + throws Throwable { + String result = (String) twoConcat.invoke(x, y); + assertThat(result).isEqualTo(expectedResult); + } + + @Test + public void twoConcat_StringAndObject( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "twoConcat", + memberDescriptor = "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;") + MethodHandle twoConcat) + throws Throwable { + String result = (String) twoConcat.invoke("ab", (Object) "cd"); + assertThat(result).isEqualTo("T:abcd"); + } + + @Test + public void twoConcatWithConstants( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "twoConcatWithConstants", + memberDescriptor = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") + MethodHandle twoConcat) + throws Throwable { + String result = (String) twoConcat.invoke("ab", "cd"); + assertThat(result).isEqualTo("abcd"); + } + + @Test + public void threeConcat( + @RuntimeMethodHandle(className = "StringConcatTestCases", memberName = "threeConcat") + MethodHandle threeConcat) + throws Throwable { + String result = (String) threeConcat.invoke("ab", "cd", "ef"); + assertThat(result).isEqualTo("abcdef"); + } + + @Test + public void twoConcatWithRecipe( + @RuntimeMethodHandle(className = "StringConcatTestCases", memberName = "twoConcatWithRecipe") + MethodHandle twoConcat) + throws Throwable { + String result = (String) twoConcat.invoke("ab", "cd"); + assertThat(result).isEqualTo("

      ab
      cd

      "); + } + + @Test + public void threeConcatWithRecipe( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "threeConcatWithRecipe") + MethodHandle threeConcat) + throws Throwable { + String result = (String) threeConcat.invoke("ab", "cd", "ef"); + assertThat(result).isEqualTo("

      ab
      cd
      ef

      "); + } + + @Test + @ParameterValueSource({"a", "0", "a0"}) + @ParameterValueSource({"a", "1", "a1"}) + @ParameterValueSource({"b", "3", "b3"}) + @ParameterValueSource({"c", "7", "c7"}) + @ParameterValueSource({"a", "15", "a15"}) + @ParameterValueSource({"d", "2147483647", "d2147483647"}) // Integer.MAX_VALUE + @ParameterValueSource({"d", "-2147483648", "d-2147483648"}) // Integer.MIN_VALUE + public void twoConcatWithPrimitives_StringAndInt( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "twoConcatWithPrimitives", + memberDescriptor = "(Ljava/lang/String;I)Ljava/lang/String;") + MethodHandle twoConcat, + @FromParameterValueSource String x, + @FromParameterValueSource int y, + @FromParameterValueSource String expectedResult) + throws Throwable { + String result = (String) twoConcat.invoke(x, y); + assertThat(result).isEqualTo(expectedResult); + } + + @Test + @ParameterValueSource({"a", "1", "a1"}) + @ParameterValueSource({"b", "3", "b3"}) + @ParameterValueSource({"c", "7", "c7"}) + @ParameterValueSource({"a", "15", "a15"}) + @ParameterValueSource({"d", "9223372036854775807", "d9223372036854775807"}) // Long.MAX_VALUE + @ParameterValueSource({"e", "-9223372036854775808", "e-9223372036854775808"}) // Long.MIN_VALUE + public void twoConcatWithPrimitives_StringAndLong( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "twoConcatWithPrimitives", + memberDescriptor = "(Ljava/lang/String;J)Ljava/lang/String;") + MethodHandle twoConcat, + @FromParameterValueSource String x, + @FromParameterValueSource long y, + @FromParameterValueSource String expectedResult) + throws Throwable { + String result = (String) twoConcat.invoke(x, y); + assertThat(result).isEqualTo(expectedResult); + } + + @Test + @ParameterValueSource({"a", "123.125", "a123.125"}) + @ParameterValueSource({ + "max-double-", + "1.7976931348623157E+308", + "max-double-1.7976931348623157E308" + }) + @ParameterValueSource({ + "min-double-", + "2.2250738585072014E-308", + "min-double-2.2250738585072014E-308" + }) + public void twoConcatWithPrimitives_StringAndDouble( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "twoConcatWithPrimitives", + memberDescriptor = "(Ljava/lang/String;D)Ljava/lang/String;") + MethodHandle twoConcat, + @FromParameterValueSource String x, + @FromParameterValueSource double y, + @FromParameterValueSource String expectedResult) + throws Throwable { + String result = (String) twoConcat.invoke(x, y); + assertThat(result).isEqualTo(expectedResult); + } + + @Test + public void twoConcatWithPrimitives_intAndString( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "twoConcatWithPrimitives", + memberDescriptor = "(ILjava/lang/String;)Ljava/lang/String;") + MethodHandle twoConcat) + throws Throwable { + String result = (String) twoConcat.invoke(123, "ABC"); + assertThat(result).isEqualTo("123ABC"); + } + + @Test + public void twoConcatWithPrimitives_longAndString( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "twoConcatWithPrimitives", + memberDescriptor = "(JLjava/lang/String;)Ljava/lang/String;") + MethodHandle twoConcat) + throws Throwable { + String result = (String) twoConcat.invoke(123L, "ABC"); + assertThat(result).isEqualTo("123ABC"); + } + + @Test + public void twoConcatWithPrimitives_doubleAndString( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "twoConcatWithPrimitives", + memberDescriptor = "(DLjava/lang/String;)Ljava/lang/String;") + MethodHandle twoConcat) + throws Throwable { + String result = (String) twoConcat.invoke(123.125, "ABC"); + assertThat(result).isEqualTo("123.125ABC"); + } + + @Test + @ParameterValueSource({ + "head", // string value + "1", // int value + "true", // boolean value + "2", // byte value + "c", // char value + "4", // short value + "5.25", // double value + "6.5F", // float value + "7", // long value + "head/1/true/2/c/4/5.25/6.5/7" // expectedResult + }) + @ParameterValueSource({ + "min:", // string value + "-2147483648", // int value + "false", // boolean value + "-128", // byte value + "~", // char value + "-32768", // short value + "1.7976931348623157E+308", // double value + "1.4E-45F", // float value + "-9223372036854775808", // long value + "min:/-2147483648/false/-128/~/-32768/1.7976931348623157E308/1.4E-45/-9223372036854775808" + }) + @ParameterValueSource({ + "max:", // string value + "2147483647", // int value + "true", // boolean value + "127", // byte value + "~", // char value + "32767", // short value + "2.2250738585072014E-308", // double value + "3.4028235E+38F", // float value + "9223372036854775807", // long value + "max:/2147483647/true/127/~/32767/2.2250738585072014E-308/3.4028235E38/9223372036854775807" + }) + public void concatWithAllPrimitiveTypes( + @RuntimeMethodHandle( + className = "StringConcatTestCases", + memberName = "concatWithAllPrimitiveTypes") + MethodHandle concatWithAllPrimitiveTypes, + @FromParameterValueSource String stringValue, + @FromParameterValueSource int intValue, + @FromParameterValueSource boolean booleanValue, + @FromParameterValueSource byte byteValue, + @FromParameterValueSource char charValue, + @FromParameterValueSource short shortValue, + @FromParameterValueSource double doubleValue, + @FromParameterValueSource float floatValue, + @FromParameterValueSource long longValue, + @FromParameterValueSource String expectedResult) + throws Throwable { + String result = + (String) + concatWithAllPrimitiveTypes.invoke( + stringValue, + intValue, + booleanValue, + byteValue, + charValue, + shortValue, + doubleValue, + floatValue, + longValue); + assertThat(result).isEqualTo(expectedResult); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/stringconcat/StringConcatTestCases.java b/src/test/java/com/google/devtools/build/android/desugar/stringconcat/StringConcatTestCases.java new file mode 100644 index 00000000000000..20a1a2f73227b5 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/stringconcat/StringConcatTestCases.java @@ -0,0 +1,106 @@ +/* + * Copyright 2019 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.stringconcat; + +/** Test cases for string concatenations. */ +public final class StringConcatTestCases { + + private static final String TEXT_CONSTANT = ""; + + public static String simplePrefix(String content) { + return "prefix:" + content; + } + + public static String twoConcat(String x, String y) { + return x + y; + } + + public static String twoConcat(String x, T y) { + return "T:" + x + y; + } + + public static String threeConcat(String x, String y, String z) { + return x + y + z; + } + + public static String twoConcatWithConstants(String x, String y) { + return x + TEXT_CONSTANT + y; + } + + public static String twoConcatWithRecipe(String x, String y) { + return "

      " + x + "
      " + y + "

      "; + } + + public static String twoConcatWithPrimitives(String x, int y) { + return x + y; + } + + public static String twoConcatWithPrimitives(String x, long y) { + return x + y; + } + + public static String twoConcatWithPrimitives(String x, double y) { + return x + y; + } + + public static String twoConcatWithPrimitives(int x, String y) { + return x + y; + } + + public static String twoConcatWithPrimitives(long x, String y) { + return x + y; + } + + public static String twoConcatWithPrimitives(double x, String y) { + return x + y; + } + + public static String threeConcatWithRecipe(String x, String y, String z) { + return "

      " + x + "
      " + y + "
      " + z + "

      "; + } + + public static String concatWithAllPrimitiveTypes( + String stringValue, + int intValue, + boolean booleanValue, + byte byteValue, + char charValue, + short shortValue, + double doubleValue, + float floatValue, + long longValue) { + return stringValue + + '/' + + intValue + + '/' + + booleanValue + + '/' + + byteValue + + '/' + + charValue + + '/' + + shortValue + + '/' + + doubleValue + + '/' + + floatValue + + '/' + + longValue; + } + + private StringConcatTestCases() {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testing/junit/BUILD b/src/test/java/com/google/devtools/build/android/desugar/testing/junit/BUILD new file mode 100644 index 00000000000000..9132da9cbaa00f --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testing/junit/BUILD @@ -0,0 +1,69 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_test") + +# Description: +# Tests for the Java 8 desugaring tool for Android. +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +java_test( + name = "DesugarRuleTest", + srcs = ["DesugarRuleTest.java"], + data = [ + ":desugar_rule_test_target", + ":test_source", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_jar=$(location :desugar_rule_test_target)", + "-Dinput_srcs=$(location :test_source)", + # Required by Desugar#verifyLambdaDumpDirectoryRegistered + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + ], + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/io", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:jsr330_inject", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_library( + name = "desugar_rule_test_target", + testonly = 1, + srcs = ["DesugarRuleTestTarget.java"], +) + +# DO NOT depend on this target, kept for build verification only. +java_library( + name = "test_source_lib", + srcs = [":test_source"], + javacopts = [ + "-source 11", + "-target 11", + ], + neverlink = 1, + visibility = ["//visibility:private"], +) + +filegroup( + name = "test_source", + srcs = ["DesugarRuleTestSourceTarget.java"], +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTest.java b/src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTest.java new file mode 100644 index 00000000000000..572e1f5d3340b6 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTest.java @@ -0,0 +1,236 @@ +/* + * Copyright 2019 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.testing.junit; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.devtools.build.android.desugar.io.JarItem; +import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle.MemberUseContext; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.nio.file.Paths; +import java.util.Arrays; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +/** The test for {@link DesugarRule}. */ +@JdkSuppress(minJdkVersion = JdkVersion.V11) +@RunWith(DesugarRunner.class) +public final class DesugarRuleTest { + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, MethodHandles.lookup()) + .addInputs(Paths.get(System.getProperty("input_jar"))) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .enableIterativeTransformation(3) + .setWorkingJavaPackage("com.google.devtools.build.android.desugar.testing.junit") + .build(); + + @Inject + @DynamicClassLiteral("DesugarRuleTestTarget$InterfaceSubjectToDesugar") + private Class interfaceSubjectToDesugarRound1; + + @Inject + @DynamicClassLiteral(value = "DesugarRuleTestTarget$InterfaceSubjectToDesugar", round = 2) + private Class interfaceSubjectToDesugarRound2; + + @Inject + @DynamicClassLiteral("DesugarRuleTestTarget$InterfaceSubjectToDesugar") + private Class interfaceSubjectToDesugarFromSimpleClassName; + + @Inject + @DynamicClassLiteral( + "com.google.devtools.build.android.desugar.testing.junit.DesugarRuleTestTarget$InterfaceSubjectToDesugar") + private Class interfaceSubjectToDesugarFromQualifiedClassName; + + @Inject + @DynamicClassLiteral("DesugarRuleTestTarget$InterfaceSubjectToDesugar$$CC") + private Class interfaceSubjectToDesugarCompanionClassRound1; + + @Inject + @DynamicClassLiteral(value = "DesugarRuleTestTarget$InterfaceSubjectToDesugar$$CC", round = 2) + private Class interfaceSubjectToDesugarCompanionClassRound2; + + @Inject + @AsmNode(className = "DesugarRuleTestTarget") + private ClassNode desugarRuleTestTargetClassNode; + + @Inject + @AsmNode(className = "DesugarRuleTestTarget$Alpha", memberName = "twoIntSum") + private MethodNode twoIntSum; + + @Inject + @AsmNode( + className = "DesugarRuleTestTarget$Alpha", + memberName = "multiplier", + memberDescriptor = "J") + private FieldNode multiplier; + + @Inject + @RuntimeMethodHandle(className = "DesugarRuleTestTarget$Alpha", memberName = "") + private MethodHandle alphaConstructor; + + @Inject + @RuntimeMethodHandle( + className = "DesugarRuleTestTarget$Alpha", + memberName = "linearLongTransform") + private MethodHandle linearLongTransform; + + @Inject + @RuntimeMethodHandle( + className = "DesugarRuleTestTarget$Alpha", + memberName = "multiplier", + usage = MemberUseContext.FIELD_GETTER) + private MethodHandle alphaMultiplierGetter; + + @Inject + @RuntimeMethodHandle( + className = "DesugarRuleTestTarget$Alpha", + memberName = "multiplier", + usage = MemberUseContext.FIELD_SETTER) + private MethodHandle alphaMultiplierSetter; + + @Test + public void staticMethodsAreMovedFromOriginatingClass() { + assertThrows( + NoSuchMethodException.class, + () -> interfaceSubjectToDesugarRound1.getDeclaredMethod("staticMethod")); + } + + @Test + public void staticMethodsAreMovedFromOriginatingClass_desugarTwice() { + assertThrows( + NoSuchMethodException.class, + () -> interfaceSubjectToDesugarRound2.getDeclaredMethod("staticMethod")); + } + + @Test + public void staticMethodsAreMovedToCompanionClass() { + assertThat( + Arrays.stream(interfaceSubjectToDesugarCompanionClassRound1.getDeclaredMethods()) + .map(Method::getName)) + .contains("staticMethod$$STATIC$$"); + } + + @Test + public void innerClasses() { + assertThat( + desugarRuleTestTargetClassNode.innerClasses.stream().map(classNode -> classNode.name)) + .contains( + "com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTestTarget$InterfaceSubjectToDesugar"); + } + + @Test + public void idempotencyOperation( + @RuntimeJarEntry( + value = "DesugarRuleTestTarget$InterfaceSubjectToDesugar$$CC.class", + round = 1) + JarItem interfaceSubjectToDesugarJarEntryRound1, + @RuntimeJarEntry( + value = "DesugarRuleTestTarget$InterfaceSubjectToDesugar$$CC.class", + round = 2) + JarItem interfaceSubjectToDesugarJarEntryRound2) { + assertThat(interfaceSubjectToDesugarJarEntryRound1.jarEntry().getCrc()) + .isEqualTo(interfaceSubjectToDesugarJarEntryRound2.jarEntry().getCrc()); + } + + @Test + public void classLoaders_sameInstanceInSameRound() { + assertThat(interfaceSubjectToDesugarRound1.getClassLoader()) + .isSameInstanceAs(interfaceSubjectToDesugarCompanionClassRound1.getClassLoader()); + assertThat(interfaceSubjectToDesugarRound2.getClassLoader()) + .isSameInstanceAs(interfaceSubjectToDesugarCompanionClassRound2.getClassLoader()); + } + + @Test + public void classLiterals_requestedWithFullQualifiedClassName() { + assertThat(interfaceSubjectToDesugarFromQualifiedClassName) + .isSameInstanceAs(interfaceSubjectToDesugarFromSimpleClassName); + } + + @Test + public void injectClassLiteralsFromParam( + @DynamicClassLiteral("DesugarRuleTestTarget$InterfaceSubjectToDesugar") + Class interfaceSubjectToDesugarParam) { + assertThat(interfaceSubjectToDesugarParam).isSameInstanceAs(interfaceSubjectToDesugarRound1); + } + + @Test + public void injectFieldNodes() { + assertThat(twoIntSum.desc).isEqualTo("(II)I"); + } + + @Test + public void injectMethodNodes() { + assertThat(multiplier.desc).isEqualTo("J"); + } + + @Test + @ParameterValueSource({"1", "2", "3"}) + @ParameterValueSource({"100", "400", "500"}) + public void invokeStaticMethodHandle( + @RuntimeMethodHandle(className = "DesugarRuleTestTarget$Alpha", memberName = "twoIntSum") + MethodHandle twoIntSum, + @FromParameterValueSource int x, + @FromParameterValueSource int y, + @FromParameterValueSource int expectedResult) + throws Throwable { + int result = (int) twoIntSum.invoke(x, y); + assertThat(result).isEqualTo(expectedResult); + } + + @Test + public void invokeVirtualMethodHandle() throws Throwable { + long result = (long) linearLongTransform.invoke(alphaConstructor.invoke(1000, 2), 3L); + assertThat(result).isEqualTo(3002); + } + + @Test + public void invokeFieldGetter() throws Throwable { + Object alpha = alphaConstructor.invoke(1000, 2); + long result = (long) alphaMultiplierGetter.invoke(alpha); + assertThat(result).isEqualTo(1000); + } + + @Test + public void invokeFieldSetter() throws Throwable { + Object alpha = alphaConstructor.invoke(1000, 2); + alphaMultiplierSetter.invoke(alpha, 1111); + + long result = (long) alphaMultiplierGetter.invoke(alpha); + assertThat(result).isEqualTo(1111); + } + + @Test + public void invokeRuntimeCompiledTarget( + @RuntimeMethodHandle(className = "DesugarRuleTestSourceTarget", memberName = "twoSum") + MethodHandle twoSum) + throws Throwable { + int result = (int) twoSum.invokeExact(1, 2); + assertThat(result).isEqualTo(3); + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTestSourceTarget.java b/src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTestSourceTarget.java new file mode 100644 index 00000000000000..33fe7347d10730 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTestSourceTarget.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020 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.testing.junit; + +/** + * A Java source file to be compiled during the execution of {@link + * com.google.devtools.build.android.desugar.testing.junit.DesugarRuleTest}. + * + *

      Note: This class is used for checking the desugar pipeline and {@link + * com.google.devtools.build.android.desugar.testing.junit.DesugarRule} working as expected. DO NOT + * use this target for testing individual desugar logic. + */ +@SuppressWarnings("PrivateConstructorForUtilityClass") +public class DesugarRuleTestSourceTarget { + public static int twoSum(int x, int y) { + return x + y; + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTestTarget.java b/src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTestTarget.java new file mode 100644 index 00000000000000..7c91499fd8c7b4 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/testing/junit/DesugarRuleTestTarget.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 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.testing.junit; + +/** + * The source class used as data in {@link DesugarRuleTest}. + * + *

      Note: This class is used for checking the desugar pipeline and {@link DesugarRule} working as + * expected. DO NOT use this target for testing individual desugar logic. + */ +@SuppressWarnings({"PrivateConstructorForUtilityClass", "InterfaceWithOnlyStatics"}) // testing-only +class DesugarRuleTestTarget { + interface InterfaceSubjectToDesugar { + static void staticMethod() {} + + default int defaultMethod(int x, int y) { + return x + y; + } + } + + public static class Alpha { + + public long multiplier; + public long offset; + + public Alpha(int multiplier, int offset) { + this.multiplier = multiplier; + this.offset = offset; + } + + public static int twoIntSum(int x, int y) { + return x + y; + } + + public long linearLongTransform(long x) { + return multiplier * x + offset; + } + } +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typeannotation/AnnotationUser.java b/src/test/java/com/google/devtools/build/android/desugar/typeannotation/AnnotationUser.java new file mode 100644 index 00000000000000..3adc41ad3355a0 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typeannotation/AnnotationUser.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 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.typeannotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** The test source class for testing annotation desugaring. */ +public class AnnotationUser { + + @SuppressWarnings("unused") // Test source + public void localVarWithTypeUseAnnotation(List inTextList) { + List<@EnhancedType String> localTextList = inTextList; + } + + public void instructionTypeUseAnnotation() { + new ArrayList<@EnhancedType String>(); + } + + public void tryCatchTypeAnnotation(RuntimeException inException) { + try { + throw inException; + } catch (@EnhancedType IllegalArgumentException e) { + throw new UnsupportedOperationException(e); + } + } + + // @EnhancedVar is expected to be absent in Javac-compiled bytecode, even through @EnhancedVar is + // declared with a runtime retention policy. This test case ensures any retained annotation use + // within a method body is a type annotation. + @SuppressWarnings("unused") // Test source + public Function localNonTypeAnnotations(String inputText) { + @EnhancedVar String localText = inputText; + try { + return (@EnhancedVar Integer x) -> 2 * x; + } catch (@EnhancedVar IllegalStateException e) { + throw new UnsupportedOperationException(e); + } + } + + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @Retention(RetentionPolicy.RUNTIME) + @interface EnhancedType {} + + @Retention(RetentionPolicy.RUNTIME) + @interface EnhancedVar {} +} diff --git a/src/test/java/com/google/devtools/build/android/desugar/typeannotation/BUILD b/src/test/java/com/google/devtools/build/android/desugar/typeannotation/BUILD new file mode 100644 index 00000000000000..c513585ad75fe5 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typeannotation/BUILD @@ -0,0 +1,48 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package( + default_testonly = 1, + default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +java_test( + name = "LocalTypeAnnotationUseTest", + size = "medium", + srcs = ["LocalTypeAnnotationUseTest.java"], + data = [ + ":annotation_test_src", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing", + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar", + ], + jvm_flags = [ + "-Dinput_srcs=$(locations :annotation_test_src)", + # Required by Desugar#verifyLambdaDumpDirectoryRegistered + "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)", + "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)", + "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)", + ], + test_class = "com.google.devtools.build.android.desugar.typeannotation.LocalTypeAnnotationUseTest", + deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule", + "//third_party:asm", + "//third_party:asm-tree", + "//third_party:guava", + "//third_party:jsr305", + "//third_party:jsr330_inject", + "//third_party:junit4", + "//third_party:truth", + ], +) + +filegroup( + name = "annotation_test_src", + srcs = ["AnnotationUser.java"], +) + +filegroup( + name = "srcs", + testonly = 0, + srcs = glob(["*"]), +) diff --git a/src/test/java/com/google/devtools/build/android/desugar/typeannotation/LocalTypeAnnotationUseTest.java b/src/test/java/com/google/devtools/build/android/desugar/typeannotation/LocalTypeAnnotationUseTest.java new file mode 100644 index 00000000000000..406cc31e4bfae1 --- /dev/null +++ b/src/test/java/com/google/devtools/build/android/desugar/typeannotation/LocalTypeAnnotationUseTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 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.typeannotation; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.android.desugar.testing.junit.DesugarTestHelpers.filterInstructions; +import static org.objectweb.asm.tree.AbstractInsnNode.TYPE_INSN; + +import com.google.devtools.build.android.desugar.testing.junit.AsmNode; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRule; +import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner; +import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress; +import com.google.devtools.build.android.desugar.testing.junit.JdkVersion; +import java.lang.invoke.MethodHandles; +import java.util.Arrays; +import java.util.Objects; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.objectweb.asm.tree.MethodNode; + +/** Tests for desugaring annotations. */ +@RunWith(DesugarRunner.class) +@JdkSuppress(minJdkVersion = JdkVersion.V11) +public class LocalTypeAnnotationUseTest { + + @Rule + public final DesugarRule desugarRule = + DesugarRule.builder(this, MethodHandles.lookup()) + .addSourceInputsFromJvmFlag("input_srcs") + .addJavacOptions("--release 11") + .setWorkingJavaPackage("com.google.devtools.build.android.desugar.typeannotation") + .addCommandOptions("desugar_try_with_resources_if_needed", "true") + .build(); + + @Test + public void localNonTypeAnnotationsWithRuntimeRetention_discardedByJavac( + @AsmNode(className = "AnnotationUser", memberName = "localNonTypeAnnotations", round = 0) + MethodNode before) { + assertThat(before.visibleLocalVariableAnnotations).isNull(); + assertThat( + Arrays.stream(before.instructions.toArray()) + .map(insn -> insn.visibleTypeAnnotations) + .allMatch(Objects::isNull)) + .isTrue(); + } + + @Test + public void localVarWithTypeUseAnnotation( + @AsmNode( + className = "AnnotationUser", + memberName = "localVarWithTypeUseAnnotation", + round = 0) + MethodNode before, + @AsmNode( + className = "AnnotationUser", + memberName = "localVarWithTypeUseAnnotation", + round = 1) + MethodNode after) { + assertThat(before.visibleLocalVariableAnnotations).isNotEmpty(); + assertThat(after.visibleLocalVariableAnnotations).isNull(); + } + + @Test + public void instructionTypeUseAnnotation( + @AsmNode(className = "AnnotationUser", memberName = "instructionTypeUseAnnotation", round = 0) + MethodNode before, + @AsmNode(className = "AnnotationUser", memberName = "instructionTypeUseAnnotation", round = 1) + MethodNode after) { + assertThat(getOnlyElement(filterInstructions(before, TYPE_INSN)).visibleTypeAnnotations) + .isNotEmpty(); + assertThat(getOnlyElement(filterInstructions(after, TYPE_INSN)).visibleTypeAnnotations) + .isNull(); + } + + @Test + public void tryCatchTypeAnnotation( + @AsmNode(className = "AnnotationUser", memberName = "tryCatchTypeAnnotation", round = 0) + MethodNode before, + @AsmNode(className = "AnnotationUser", memberName = "tryCatchTypeAnnotation", round = 1) + MethodNode after) { + assertThat(getOnlyElement(before.tryCatchBlocks).visibleTypeAnnotations).isNotEmpty(); + assertThat(getOnlyElement(after.tryCatchBlocks).visibleTypeAnnotations).isNull(); + } +} From 04e8d243ead1a5de1d1e737523f5eabe5b57f7e4 Mon Sep 17 00:00:00 2001 From: Alex Humesky Date: Tue, 11 Jul 2023 19:19:43 -0400 Subject: [PATCH 6/6] Fix android_integration_test to work with the head Android tools. integration_test_setup.sh overrides the bazelrc in the test, so it must go before android_helper.sh which appends to the bazelrc to override the Android tools with the version built at head. --- src/test/shell/bazel/android/android_integration_test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/shell/bazel/android/android_integration_test.sh b/src/test/shell/bazel/android/android_integration_test.sh index 1b647c0043e254..a61fa6f461314e 100755 --- a/src/test/shell/bazel/android/android_integration_test.sh +++ b/src/test/shell/bazel/android/android_integration_test.sh @@ -26,13 +26,13 @@ # Load the test setup defined in the parent directory CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${CURRENT_DIR}/../../integration_test_setup.sh" \ + || { echo "integration_test_setup.sh not found!" >&2; exit 1; } + source "${CURRENT_DIR}/android_helper.sh" \ || { echo "android_helper.sh not found!" >&2; exit 1; } fail_if_no_android_sdk -source "${CURRENT_DIR}/../../integration_test_setup.sh" \ - || { echo "integration_test_setup.sh not found!" >&2; exit 1; } - resolve_android_toolchains "$1" function test_sdk_library_deps() {