Merge commit 'b9cfe1d0e4c97d5d6e9ffcadd57c368cede4fd13' into dev-release
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java new file mode 100644 index 0000000..a7ac9e3 --- /dev/null +++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -0,0 +1,155 @@ +// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8; + +import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException; + +import com.android.tools.r8.DexIndexedConsumer.DirectoryConsumer; +import com.android.tools.r8.dex.ApplicationReader; +import com.android.tools.r8.dex.ApplicationWriter; +import com.android.tools.r8.dex.Marker; +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexApplication; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.LazyLoadedDexApplication; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.shaking.MainDexInfo; +import com.android.tools.r8.utils.ExceptionUtils; +import com.android.tools.r8.utils.FeatureClassMapping; +import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.InternalOptions.DesugarState; +import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +@Keep +public final class DexSplitterHelper { + + public static void run( + D8Command command, FeatureClassMapping featureClassMapping, String output, String proguardMap) + throws CompilationFailedException { + ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED); + try { + ExceptionUtils.withCompilationHandler( + command.getReporter(), + () -> run(command, featureClassMapping, output, proguardMap, executor)); + } finally { + executor.shutdown(); + } + } + + public static void run( + D8Command command, + FeatureClassMapping featureClassMapping, + String output, + String proguardMap, + ExecutorService executor) + throws IOException { + InternalOptions options = command.getInternalOptions(); + options.desugarState = DesugarState.OFF; + options.enableMainDexListCheck = false; + options.ignoreMainDexMissingClasses = true; + options.minimalMainDex = false; + assert !options.isMinifying(); + options.inlinerOptions().enableInlining = false; + options.outline.enabled = false; + + try { + Timing timing = new Timing("DexSplitter"); + ApplicationReader applicationReader = + new ApplicationReader(command.getInputApp(), options, timing); + DexApplication app = applicationReader.read(executor); + MainDexInfo mainDexInfo = applicationReader.readMainDexClasses(app); + + List<Marker> markers = app.dexItemFactory.extractMarkers(); + + ClassNameMapper mapper = null; + if (proguardMap != null) { + mapper = ClassNameMapper.mapperFromFile(Paths.get(proguardMap)); + } + Map<String, LazyLoadedDexApplication.Builder> applications = + getDistribution(app, featureClassMapping, mapper); + for (Entry<String, LazyLoadedDexApplication.Builder> entry : applications.entrySet()) { + String feature = entry.getKey(); + timing.begin("Feature " + feature); + DexApplication featureApp = entry.getValue().build(); + assert !options.hasMethodsFilter(); + + // If this is the base, we add the main dex list. + AppInfo appInfo = + feature.equals(featureClassMapping.getBaseName()) + ? AppInfo.createInitialAppInfo(featureApp, mainDexInfo) + : AppInfo.createInitialAppInfo(featureApp); + AppView<AppInfo> appView = AppView.createForD8(appInfo); + + // Run d8 optimize to ensure jumbo strings are handled. + D8.optimize(appView, options, timing, executor); + + // We create a specific consumer for each split. + Path outputDir = Paths.get(output).resolve(entry.getKey()); + if (!Files.exists(outputDir)) { + Files.createDirectory(outputDir); + } + DexIndexedConsumer consumer = new DirectoryConsumer(outputDir); + + try { + new ApplicationWriter( + appView, + markers, + NamingLens.getIdentityLens(), + consumer) + .write(executor); + options.printWarnings(); + } finally { + consumer.finished(options.reporter); + } + timing.end(); + } + } catch (ExecutionException e) { + throw unwrapExecutionException(e); + } catch (FeatureMappingException e) { + options.reporter.error(e.getMessage()); + } finally { + options.signalFinishedToConsumers(); + } + } + + private static Map<String, LazyLoadedDexApplication.Builder> getDistribution( + DexApplication app, FeatureClassMapping featureClassMapping, ClassNameMapper mapper) + throws FeatureMappingException { + Map<String, LazyLoadedDexApplication.Builder> applications = new HashMap<>(); + for (DexProgramClass clazz : app.classes()) { + String clazzName = + mapper != null ? mapper.deobfuscateClassName(clazz.toString()) : clazz.toString(); + String feature = featureClassMapping.featureForClass(clazzName); + LazyLoadedDexApplication.Builder featureApplication = applications.get(feature); + if (featureApplication == null) { + featureApplication = DexApplication.builder(app.options, app.timing); + applications.put(feature, featureApplication); + } + featureApplication.addProgramClass(clazz); + } + return applications; + } + + public static void runD8ForTesting(D8Command command, boolean dontCreateMarkerInD8) + throws CompilationFailedException { + InternalOptions options = command.getInternalOptions(); + options.testing.dontCreateMarkerInD8 = dontCreateMarkerInD8; + D8.runForTesting(command.getInputApp(), options); + } +}
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java index 2e44e14..2d836ae 100644 --- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java +++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.bisect.Bisect; import com.android.tools.r8.cf.CfVerifierTool; import com.android.tools.r8.compatproguard.CompatProguard; +import com.android.tools.r8.dexsplitter.DexSplitter; import com.android.tools.r8.relocator.RelocatorCommandLine; import com.android.tools.r8.tracereferences.TraceReferences; import java.util.Arrays; @@ -40,6 +41,9 @@ case "dexsegments": DexSegments.main(shift(args)); break; + case "dexsplitter": + DexSplitter.main(shift(args)); + break; case "disasm": Disassemble.main(shift(args)); break;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java index af0b133..bf9dc6b 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -225,7 +225,6 @@ DexItemFactory dexItemFactory) { // ..., value1, value2 → // ..., result - FrameType frameType = FrameType.fromNumericType(type, dexItemFactory); - return state.pop(appView, frameType, frameType).push(frameType); + return state.popInitialized(appView, type).popInitialized(appView, type).push(appView, type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java index cdd43c2..e696048 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -100,7 +99,10 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., arrayref → + // ..., length + return frame + .popInitialized(appView, dexItemFactory.objectArrayType) + .push(dexItemFactory.intType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java index 5069f14..8cc8e63 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -145,7 +144,11 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., arrayref, index → + // ..., value + return frame + .popInitialized(appView, dexItemFactory.intType) + .popInitialized(appView, dexItemFactory.objectArrayType) + .push(appView, type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java index c51336f..942f633 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -136,7 +135,11 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., arrayref, index, value → + // ... + return frame + .popInitialized(appView, type) + .popInitialized(appView, dexItemFactory.intType) + .popInitialized(appView, dexItemFactory.objectArrayType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java index 55dd90f..323a253 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -149,7 +148,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., objectref → + // ..., objectref + return frame.popInitialized(appView, dexItemFactory.objectType).push(type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java index 4ca69a0..4a42ca3 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -149,7 +148,11 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., value1, value2 → + // ..., result + return frame + .popInitialized(appView, type) + .popInitialized(appView, type) + .push(dexItemFactory.intType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java index 10240e5..ff36775 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -156,7 +155,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ... → + // ..., value + return frame.push(dexItemFactory.classType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java index 666da46..54c7820 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -242,7 +242,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ... → + // ..., value + return frame.push(dexItemFactory.classType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java index 2f79cea..50564e3 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -120,7 +119,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ... → + // ..., value + return frame.push(dexItemFactory.methodHandleType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java index 0cc8d78..b262ff9 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -118,7 +117,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ... → + // ..., value + return frame.push(dexItemFactory.methodTypeType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java index efcd063..e849e05 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -90,7 +89,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ... → + // ..., value + return frame.push(DexItemFactory.nullValueType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java index cee2f4a..34f67c7 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -238,7 +237,8 @@ DexItemFactory dexItemFactory) { // ... → // ..., value - frameBuilder.push(type.toPrimitiveType().toDexType(dexItemFactory)); + assert type.isPrimitive(); + frameBuilder.push(type.toDexType(dexItemFactory)); } @Override @@ -247,7 +247,9 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ... → + // ..., value + assert type.isPrimitive(); + return frame.push(appView, type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java index 5ab3742..8bccf77 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -140,7 +139,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ... → + // ..., value + return frame.push(dexItemFactory.stringType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java index e7b8c99..4ab2860 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -156,7 +156,7 @@ } public static FrameType fromNumericType(NumericType numericType, DexItemFactory factory) { - return FrameType.initialized(numericType.dexTypeFor(factory)); + return FrameType.initialized(numericType.toDexType(factory)); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java index f6d94b7..dacbd9e 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -156,10 +156,7 @@ DexItemFactory dexItemFactory) { // ..., value1, value2 → // ... - DexType type = - this.type.isObject() - ? dexItemFactory.objectType - : this.type.toPrimitiveType().toDexType(dexItemFactory); + DexType type = this.type.toDexType(dexItemFactory); frameBuilder.popAndDiscardInitialized(type, type); frameBuilder.checkTarget(target); }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java index 4df1ecd..a168090 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -133,7 +132,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., → + // ..., value + return frame.push(dexItemFactory.intType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java index df84afd..c461c07 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.code.CfOrDexInstanceFieldRead; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.DexClassAndMethod; @@ -84,7 +83,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., objectref → + // ..., value + return frame.popInitialized(appView, getField().getHolderType()).push(getField().getType()); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java index da48878..53f9904 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -143,7 +142,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., objectref → + // ..., result + return frame.popInitialized(appView, dexItemFactory.objectType).push(dexItemFactory.intType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java index 421902e..121c288 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -187,7 +186,13 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., [arg1, [arg2 ...]] → + // ... + frame = frame.popInitialized(appView, callSite.getMethodProto().getParameters().getBacking()); + DexType returnType = callSite.getMethodProto().getReturnType(); + if (returnType.isVoidType()) { + return frame; + } + return frame.push(returnType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java index a5ee7eb..3cf6f34 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -206,7 +205,22 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., value1, value2 → + // ..., result + NumericType value1Type = type; + NumericType value2Type; + switch (opcode) { + case And: + case Or: + case Xor: + value2Type = value1Type; + break; + default: + value2Type = NumericType.INT; + } + return frame + .popInitialized(appView, value2Type) + .popInitialized(appView, value1Type) + .push(appView, value1Type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java index 85ddc69..f860f58 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -112,7 +111,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., objectref → + // ... + return frame.popInitialized(appView, dexItemFactory.objectType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java index 796a93f..a69ff20 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -149,7 +148,11 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., count1, [count2, ...] → + // ..., arrayref + for (int i = 0; i < dimensions; i++) { + frame = frame.popInitialized(appView, dexItemFactory.intType); + } + return frame.push(type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java index 9c6a7f8..c25b0ff 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -138,7 +137,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., value → + // ..., result + return frame.popInitialized(appView, type).push(appView, type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java index ba81a58..261db2e 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -173,7 +172,6 @@ DexItemFactory dexItemFactory) { // ..., count → // ..., arrayref - assert type.isArrayType(); frameBuilder.popAndDiscardInitialized(dexItemFactory.intType).push(type); } @@ -183,7 +181,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., count → + // ..., arrayref + return frame.popInitialized(appView, dexItemFactory.intType).push(type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java index 8683ddd..73d04d1 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -135,7 +134,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ... → + // ..., objectref + return frame.push(type); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java index e8b3a2e..7bc083c 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -92,7 +91,6 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + return frame; } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java index 0341487..ee83adb 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -5,7 +5,6 @@ import com.android.tools.r8.cf.CfPrinter; import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -210,7 +209,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., value → + // ..., result + return frame.popInitialized(appView, from).push(appView, to); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java index 155b17d..42ebf38 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.CfCompareHelper; @@ -125,7 +124,6 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + return frame; } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java index 4a9fd91..ba04a1c 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
@@ -5,7 +5,6 @@ package com.android.tools.r8.cf.code; import com.android.tools.r8.cf.CfPrinter; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.errors.Unreachable; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; @@ -128,7 +127,9 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + for (DexField ignored : fields) { + frame = frame.popInitialized(appView, dexItemFactory.objectType); + } + return frame.push(dexItemFactory.objectArrayType); } }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java index 5f5e5a9..c929d68 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -524,8 +524,9 @@ switch (opcode) { case Pop: { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., value → + // ... + return frame.pop(appView, FrameType.oneWord()); } case Pop2: { @@ -536,7 +537,9 @@ // ..., value → // ..., value, value return frame.pop( - appView, FrameType.oneWord(), frameType -> frame.push(frameType).push(frameType)); + appView, + FrameType.oneWord(), + (newFrame, frameType) -> newFrame.push(frameType).push(frameType)); case DupX1: { // TODO(b/214496607): Implement this. @@ -564,8 +567,16 @@ } case Swap: { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., value2, value1 → + // ..., value1, value2 + return frame.pop( + appView, + FrameType.oneWord(), + (newFrame1, frameType1) -> + newFrame1.pop( + appView, + FrameType.oneWord(), + (newFrame2, frameType2) -> newFrame2.push(frameType1).push(frameType2))); } default: throw new Unreachable("Invalid opcode for CfStackInstruction");
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java index 26114d8..3c64e6a 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.cf.code; -import com.android.tools.r8.errors.Unimplemented; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.DexClassAndMethod; @@ -83,7 +82,8 @@ ProgramMethod context, AppView<?> appView, DexItemFactory dexItemFactory) { - // TODO(b/214496607): Implement this. - throw new Unimplemented(); + // ..., value → + // ... + return frame.popInitialized(appView, getField().getType()); } }
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java index 8671b68..580d5ef 100644 --- a/src/main/java/com/android/tools/r8/code/Instruction.java +++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -343,6 +343,10 @@ @Override public final int acceptCompareTo(Instruction other, CompareToVisitor visitor) { int opcodeDiff = visitor.visitInt(getCompareToId(), other.getCompareToId()); + if (opcodeDiff != 0) { + return opcodeDiff; + } + opcodeDiff = visitor.visitInt(getOffset(), other.getOffset()); return opcodeDiff != 0 ? opcodeDiff : internalAcceptCompareTo(other, visitor); }
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java new file mode 100644 index 0000000..4ee416c --- /dev/null +++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -0,0 +1,377 @@ +// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.dexsplitter; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.D8Command; +import com.android.tools.r8.DexIndexedConsumer; +import com.android.tools.r8.DexSplitterHelper; +import com.android.tools.r8.Diagnostic; +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.Keep; +import com.android.tools.r8.origin.PathOrigin; +import com.android.tools.r8.utils.AbortException; +import com.android.tools.r8.utils.ExceptionDiagnostic; +import com.android.tools.r8.utils.ExceptionUtils; +import com.android.tools.r8.utils.FeatureClassMapping; +import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException; +import com.android.tools.r8.utils.OptionsParsing; +import com.android.tools.r8.utils.OptionsParsing.ParseContext; +import com.android.tools.r8.utils.StringDiagnostic; +import com.android.tools.r8.utils.ZipUtils; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +@Keep +public final class DexSplitter { + + private static final String DEFAULT_OUTPUT_DIR = "output"; + private static final String DEFAULT_BASE_NAME = "base"; + + private static final boolean PRINT_ARGS = false; + + public static class FeatureJar { + private String jar; + private String outputName; + + public FeatureJar(String jar, String outputName) { + this.jar = jar; + this.outputName = outputName; + } + + public FeatureJar(String jar) { + this(jar, featureNameFromJar(jar)); + } + + public String getJar() { + return jar; + } + + public String getOutputName() { + return outputName; + } + + private static String featureNameFromJar(String jar) { + Path jarPath = Paths.get(jar); + String featureName = jarPath.getFileName().toString(); + if (featureName.endsWith(".jar") || featureName.endsWith(".zip")) { + featureName = featureName.substring(0, featureName.length() - 4); + } + return featureName; + } + } + + private static class ZipFileOrigin extends PathOrigin { + + public ZipFileOrigin(Path path) { + super(path); + } + + @Override + public String part() { + return "splitting of file '" + super.part() + "'"; + } + } + + @Keep + public static final class Options { + private final DiagnosticsHandler diagnosticsHandler; + private List<String> inputArchives = new ArrayList<>(); + private List<FeatureJar> featureJars = new ArrayList<>(); + private List<String> baseJars = new ArrayList<>(); + private String baseOutputName = DEFAULT_BASE_NAME; + private String output = DEFAULT_OUTPUT_DIR; + private String featureSplitMapping; + private String proguardMap; + private String mainDexList; + private boolean splitNonClassResources = false; + + public Options() { + this(new DiagnosticsHandler() {}); + } + + public Options(DiagnosticsHandler diagnosticsHandler) { + this.diagnosticsHandler = diagnosticsHandler; + } + + public DiagnosticsHandler getDiagnosticsHandler() { + return diagnosticsHandler; + } + + public String getMainDexList() { + return mainDexList; + } + + public void setMainDexList(String mainDexList) { + this.mainDexList = mainDexList; + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } + + public String getFeatureSplitMapping() { + return featureSplitMapping; + } + + public void setFeatureSplitMapping(String featureSplitMapping) { + this.featureSplitMapping = featureSplitMapping; + } + + public String getProguardMap() { + return proguardMap; + } + + public void setProguardMap(String proguardMap) { + this.proguardMap = proguardMap; + } + + public String getBaseOutputName() { + return baseOutputName; + } + + public void setBaseOutputName(String baseOutputName) { + this.baseOutputName = baseOutputName; + } + + public void addInputArchive(String inputArchive) { + inputArchives.add(inputArchive); + } + + public void addBaseJar(String baseJar) { + baseJars.add(baseJar); + } + + private void addFeatureJar(FeatureJar featureJar) { + featureJars.add(featureJar); + } + + public void addFeatureJar(String jar) { + featureJars.add(new FeatureJar(jar)); + } + + public void addFeatureJar(String jar, String outputName) { + featureJars.add(new FeatureJar(jar, outputName)); + } + + public void setSplitNonClassResources(boolean value) { + splitNonClassResources = value; + } + + public ImmutableList<String> getInputArchives() { + return ImmutableList.copyOf(inputArchives); + } + + ImmutableList<FeatureJar> getFeatureJars() { + return ImmutableList.copyOf(featureJars); + } + + ImmutableList<String> getBaseJars() { + return ImmutableList.copyOf(baseJars); + } + + // Shorthand error messages. + public Diagnostic error(String msg) { + StringDiagnostic error = new StringDiagnostic(msg); + diagnosticsHandler.error(error); + return error; + } + } + + /** + * Parse a feature jar argument and return the corresponding FeatureJar representation. + * Default to use the name of the jar file if the argument contains no ':', if the argument + * contains ':', then use the value after the ':' as the name. + * @param argument + */ + private static FeatureJar parseFeatureJarArgument(String argument) { + if (argument.contains(":")) { + String[] parts = argument.split(":"); + if (parts.length > 2) { + throw new RuntimeException("--feature-jar argument contains more than one :"); + } + return new FeatureJar(parts[0], parts[1]); + } + return new FeatureJar(argument); + } + + private static Options parseArguments(String[] args) { + Options options = new Options(); + ParseContext context = new ParseContext(args); + while (context.head() != null) { + List<String> inputs = OptionsParsing.tryParseMulti(context, "--input"); + if (inputs != null) { + inputs.forEach(options::addInputArchive); + continue; + } + List<String> featureJars = OptionsParsing.tryParseMulti(context, "--feature-jar"); + if (featureJars != null) { + featureJars.forEach((feature) -> options.addFeatureJar(parseFeatureJarArgument(feature))); + continue; + } + List<String> baseJars = OptionsParsing.tryParseMulti(context, "--base-jar"); + if (baseJars != null) { + baseJars.forEach(options::addBaseJar); + continue; + } + String output = OptionsParsing.tryParseSingle(context, "--output", "-o"); + if (output != null) { + options.setOutput(output); + continue; + } + + String mainDexList= OptionsParsing.tryParseSingle(context, "--main-dex-list", null); + if (mainDexList!= null) { + options.setMainDexList(mainDexList); + continue; + } + + String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null); + if (proguardMap != null) { + options.setProguardMap(proguardMap); + continue; + } + String baseOutputName = OptionsParsing.tryParseSingle(context, "--base-output-name", null); + if (baseOutputName != null) { + options.setBaseOutputName(baseOutputName); + continue; + } + String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null); + if (featureSplit != null) { + options.setFeatureSplitMapping(featureSplit); + continue; + } + Boolean b = OptionsParsing.tryParseBoolean(context, "--split-non-class-resources"); + if (b != null) { + options.setSplitNonClassResources(b); + continue; + } + throw new RuntimeException(String.format("Unknown options: '%s'.", context.head())); + } + return options; + } + + private static FeatureClassMapping createFeatureClassMapping(Options options) + throws FeatureMappingException { + if (options.getFeatureSplitMapping() != null) { + return FeatureClassMapping.fromSpecification( + Paths.get(options.getFeatureSplitMapping()), options.getDiagnosticsHandler()); + } + assert !options.getFeatureJars().isEmpty(); + return FeatureClassMapping.Internal.fromJarFiles(options.getFeatureJars(), + options.getBaseJars(), options.getBaseOutputName(), options.getDiagnosticsHandler()); + } + + private static void run(String[] args) + throws CompilationFailedException, FeatureMappingException { + Options options = parseArguments(args); + run(options); + } + + public static void run(Options options) + throws FeatureMappingException, CompilationFailedException { + Diagnostic error = null; + if (options.getInputArchives().isEmpty()) { + error = options.error("Need at least one --input"); + } + if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) { + error = options.error("You must supply a feature split mapping or feature jars"); + } + if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) { + error = options.error("You can't supply both a feature split mapping and feature jars"); + } + if (error != null) { + throw new AbortException(error); + } + + D8Command.Builder builder = D8Command.builder(options.diagnosticsHandler); + + + for (String s : options.inputArchives) { + builder.addProgramFiles(Paths.get(s)); + } + // We set the actual consumer on the ApplicationWriter when we have calculated the distribution + // since we don't yet know the distribution. + builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer()); + if (options.getMainDexList() != null) { + builder.addMainDexListFiles(Paths.get(options.getMainDexList())); + } + + FeatureClassMapping featureClassMapping = createFeatureClassMapping(options); + + DexSplitterHelper.run( + builder.build(), featureClassMapping, options.getOutput(), options.getProguardMap()); + + if (options.splitNonClassResources) { + splitNonClassResources(options, featureClassMapping); + } + } + + private static void splitNonClassResources(Options options, + FeatureClassMapping featureClassMapping) { + for (String s : options.inputArchives) { + try (ZipFile zipFile = new ZipFile(s, StandardCharsets.UTF_8)) { + Enumeration<? extends ZipEntry> entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + if (!ZipUtils.isDexFile(name) && !ZipUtils.isClassFile(name)) { + String feature = featureClassMapping.featureForNonClass(name); + Path outputDir = Paths.get(options.getOutput()).resolve(feature); + try (InputStream stream = zipFile.getInputStream(entry)) { + Path outputFile = outputDir.resolve(name); + Path parent = outputFile.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + Files.copy(stream, outputFile); + } + } + } + } catch (IOException e) { + ExceptionDiagnostic error = new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s))); + options.getDiagnosticsHandler().error(error); + throw new AbortException(error); + } + } + } + + public static void main(String[] args) { + if (PRINT_ARGS) { + printArgs(args); + } + ExceptionUtils.withMainProgramHandler( + () -> { + try { + run(args); + } catch (FeatureMappingException e) { + // TODO(ricow): Report feature mapping errors via the reporter. + throw new RuntimeException("Splitting failed: " + e.getMessage()); + } + }); + } + + private static void printArgs(String[] args) { + System.err.printf("r8.DexSplitter"); + for (String s : args) { + System.err.printf(" %s", s); + } + System.err.println(""); + } +}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java index 9787686..2e6cc06 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -228,6 +228,7 @@ new IRCode( appView.options(), method, + callerPosition, ListUtils.newLinkedList(block), valueNumberGenerator, blockNumberGenerator,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java index 9856e0b..0b2c884 100644 --- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java +++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -5,6 +5,7 @@ package com.android.tools.r8.horizontalclassmerging.policies; import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull; +import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod; import static com.android.tools.r8.utils.MapUtils.ignoreKey; @@ -26,6 +27,7 @@ import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions; import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.TraversalContinuation; +import com.android.tools.r8.utils.WorkList; import com.android.tools.r8.utils.collections.ProgramMethodSet; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -321,10 +323,6 @@ worklist.clear(); } - boolean markClassInitializerAsSeen(DexProgramClass clazz) { - return seenClassInitializers.add(clazz); - } - boolean enqueueMethod(ProgramMethod method) { if (seenMethods.add(method)) { worklist.addLast(method); @@ -434,44 +432,38 @@ return appView.appInfo().isSubtype(getContext().getHolder(), clazz); } - private void triggerClassInitializer(DexType type) { - DexProgramClass clazz = type.asProgramClass(appView); - if (clazz != null) { - triggerClassInitializer(clazz); - } - } - - private void triggerClassInitializer(DexProgramClass clazz) { - if (!markClassInitializerAsSeen(clazz)) { - return; - } - - if (groupMembers.contains(clazz)) { - if (hasSingleTracingRoot(clazz)) { - // We found an execution path from the class initializer of the given class back to its - // own class initializer. Therefore this class is not eligible for merging. - fail(); - } else { - // Record that this class initializer is reachable from the tracing roots. - recordClassInitializerReachableFromTracingRoots(clazz); + private void triggerClassInitializer(DexProgramClass root) { + WorkList<DexProgramClass> worklist = WorkList.newWorkList(seenClassInitializers); + worklist.addIfNotSeen(root); + while (worklist.hasNext()) { + DexProgramClass clazz = worklist.next(); + if (groupMembers.contains(clazz)) { + if (hasSingleTracingRoot(clazz)) { + // We found an execution path from the class initializer of the given class back to + // its own class initializer. Therefore this class is not eligible for merging. + fail(); + } else { + // Record that this class initializer is reachable from the tracing roots. + recordClassInitializerReachableFromTracingRoots(clazz); + } } - } - ProgramMethod classInitializer = clazz.getProgramClassInitializer(); - if (classInitializer != null) { - if (!enqueueMethod(classInitializer)) { + ProgramMethod classInitializer = clazz.getProgramClassInitializer(); + if (classInitializer != null && !enqueueMethod(classInitializer)) { // This class initializer is already seen in the current context, thus all of the parent // class initializers are also seen in the current context. return; } - } - triggerClassInitializer(clazz.getSuperType()); + DexProgramClass superClass = + asProgramClassOrNull(appView.definitionFor(clazz.getSuperType())); + if (superClass != null) { + worklist.addIfNotSeen(superClass); + } - MergeGroup other = allGroups.get(clazz); - if (other != null && other != group) { - for (DexProgramClass member : other) { - triggerClassInitializer(member); + MergeGroup other = allGroups.get(clazz); + if (other != null && other != group) { + worklist.addIfNotSeen(other); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java index bf8f7ad..4656ad8 100644 --- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java +++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -117,6 +117,7 @@ private final ProgramMethod method; private final MutableMethodConversionOptions conversionOptions; + public final Position entryPosition; public LinkedList<BasicBlock> blocks; public final NumberGenerator valueNumberGenerator; public final NumberGenerator basicBlockNumberGenerator; @@ -133,6 +134,7 @@ public IRCode( InternalOptions options, ProgramMethod method, + Position entryPosition, LinkedList<BasicBlock> blocks, NumberGenerator valueNumberGenerator, NumberGenerator basicBlockNumberGenerator, @@ -142,9 +144,11 @@ assert metadata != null; assert options != null; assert blocks.size() == basicBlockNumberGenerator.peek(); + assert entryPosition != null; this.options = options; this.conversionOptions = conversionOptions; this.method = method; + this.entryPosition = entryPosition; this.blocks = blocks; this.valueNumberGenerator = valueNumberGenerator; this.basicBlockNumberGenerator = basicBlockNumberGenerator; @@ -169,6 +173,10 @@ return blocks.getFirst(); } + public Position getEntryPosition() { + return entryPosition; + } + public MethodConversionOptions getConversionOptions() { return conversionOptions; } @@ -516,7 +524,9 @@ } public void removeBlocks(Collection<BasicBlock> blocksToRemove) { - blocks.removeAll(blocksToRemove); + if (!blocksToRemove.isEmpty()) { + blocks.removeAll(blocksToRemove); + } } /** @@ -1497,34 +1507,4 @@ } } } - - public Position findFirstNonNonePosition() { - return findFirstNonNonePosition(Position.none()); - } - - public Position findFirstNonNonePosition(Position orElse) { - BasicBlock current = entryBlock(); - Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet(); - do { - boolean changed = visitedBlocks.add(current); - assert changed; - - for (Instruction instruction : current.getInstructions()) { - if (instruction.isArgument() || instruction.isGoto()) { - continue; - } - if (instruction.getPosition().isSome()) { - return instruction.getPosition(); - } - } - - // The very first non-argument instruction can be chained via goto. - if (current.exit().isGoto()) { - current = current.exit().asGoto().getTarget(); - } else { - break; - } - } while (!visitedBlocks.contains(current)); - return orElse; - } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumericType.java b/src/main/java/com/android/tools/r8/ir/code/NumericType.java index 99456dc..85b80b5 100644 --- a/src/main/java/com/android/tools/r8/ir/code/NumericType.java +++ b/src/main/java/com/android/tools/r8/ir/code/NumericType.java
@@ -16,7 +16,7 @@ FLOAT, DOUBLE; - public DexType dexTypeFor(DexItemFactory factory) { + public DexType toDexType(DexItemFactory factory) { switch (this) { case BYTE: return factory.byteType;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java index 0ae9459..4677bd4 100644 --- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java +++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.errors.InternalCompilerError; import com.android.tools.r8.errors.Unreachable; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement; import com.android.tools.r8.ir.analysis.type.TypeElement; @@ -38,6 +39,10 @@ return this == OBJECT; } + public boolean isPrimitive() { + return !isObject(); + } + public boolean isSingle() { return this == INT || this == FLOAT; } @@ -138,6 +143,23 @@ throw new Unreachable("Unexpected conversion of imprecise type: " + type); } + public DexType toDexType(DexItemFactory dexItemFactory) { + switch (this) { + case OBJECT: + return dexItemFactory.objectType; + case INT: + return dexItemFactory.intType; + case FLOAT: + return dexItemFactory.floatType; + case LONG: + return dexItemFactory.longType; + case DOUBLE: + return dexItemFactory.doubleType; + default: + throw new Unreachable(); + } + } + public PrimitiveTypeElement toPrimitiveType() { switch (this) { case INT:
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java index 1a39011..5270193 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -412,6 +412,11 @@ if (needsGeneratedMethodSynchronization) { buildMethodEnterSynchronization(builder); } + Position entryPosition = getCanonicalDebugPositionAtOffset(0); + if (!state.getPosition().equals(entryPosition)) { + state.setPosition(entryPosition); + builder.addDebugPosition(state.getPosition()); + } recordStateForTarget(0, state.getSnapshot()); inPrelude = false; }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java index 2bd4cfa..de631d3 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -54,7 +54,6 @@ import com.android.tools.r8.ir.code.InstructionIterator; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.IntSwitch; -import com.android.tools.r8.ir.code.JumpInstruction; import com.android.tools.r8.ir.code.Move; import com.android.tools.r8.ir.code.NewArrayFilledData; import com.android.tools.r8.ir.code.Position; @@ -72,7 +71,7 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; +import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -351,6 +350,50 @@ && currentBlock.getPredecessors().get(0) == previousBlock; } + private static void removeTrivialFallthroughBlocks(IRCode code) { + for (int blockIndex = 1; blockIndex < code.blocks.size() - 1; blockIndex++) { + // We skip the last block as it has no fallthrough. We also skip checking the entry block as + // it has no predecessors and must define the initial position. Any subsequent block must be + // statically reachable and thus have predecessors. + BasicBlock currentBlock = code.blocks.get(blockIndex); + assert !currentBlock.getPredecessors().isEmpty(); + if (currentBlock.size() != 2) { + continue; + } + DebugPosition debugPosition = currentBlock.entry().asDebugPosition(); + com.android.tools.r8.ir.code.Goto exit = currentBlock.exit().asGoto(); + if (debugPosition == null || exit == null || debugPosition.getPosition().isNone()) { + continue; + } + BasicBlock nextBlock = code.blocks.get(blockIndex + 1); + if (exit.getTarget() != nextBlock) { + continue; + } + // The block is a trivial position block that falls through to the following. + // If the position is equal to each predecessor block then the line is already active on + // each entry to the fallthrough and it can be safely removed. + boolean allMatch = true; + Position position = debugPosition.getPosition(); + for (BasicBlock pred : currentBlock.getPredecessors()) { + // Do to the target == next check this cannot be a trivial loop. + assert pred != currentBlock; + Position predExit = pred.exit().getPosition(); + if (!position.equals(predExit)) { + allMatch = false; + break; + } + } + if (allMatch) { + currentBlock.removeInstruction(debugPosition); + CodeRewriter.unlinkTrivialGotoBlock(currentBlock, nextBlock); + code.removeBlocks(Collections.singleton(currentBlock)); + // Having removed the block at blockIndex, the previous block may now be a trivial + // fallthrough. Rewind to that point and retry. This avoids iterating to a fixed point. + blockIndex = Math.max(0, blockIndex - 2); + } + } + } + // Eliminates unneeded debug positions. // // After this pass all remaining debug positions mark places where we must ensure a materializing @@ -359,6 +402,12 @@ if (!code.metadata().mayHaveDebugPosition()) { return; } + + // We must start by removing any blocks that are already trivial fallthrough blocks with no + // position change. With these removed it is then sound to make the fallthrough judgement when + // determining if a goto will materialize or not. + removeTrivialFallthroughBlocks(code); + // Current position known to have a materializing instruction associated with it. Position currentMaterializedPosition = Position.none(); @@ -373,7 +422,6 @@ // Compute the set of all positions that can be removed. // (Delaying removal to avoid ConcurrentModificationException). List<DebugPosition> toRemove = new ArrayList<>(); - Set<BasicBlock> trivialBlocks = new HashSet<>(); for (int blockIndex = 0; blockIndex < code.blocks.size(); blockIndex++) { BasicBlock currentBlock = code.blocks.get(blockIndex); @@ -425,12 +473,11 @@ && currentMaterializedPosition == instruction.getPosition()) { // Here we don't need to check locals state as the line is already active. toRemove.add(instruction.asDebugPosition()); - if (currentBlock.getInstructions().size() == 2) { - JumpInstruction exit = currentBlock.exit(); - if (exit.isGoto() && exit.getPosition() == currentMaterializedPosition) { - trivialBlocks.add(currentBlock); - } - } + assert currentBlock.size() != 2 + || currentBlock.exit().getPosition() != currentMaterializedPosition + || !currentBlock.exit().isGoto() + || currentBlock.exit().asGoto().getTarget() != nextBlock + : "Unexpected trivial fallthrough block. This should be removed already."; } else if (unresolvedPosition != null && unresolvedPosition.getPosition() == instruction.getPosition() && locals.equals(localsAtUnresolvedPosition)) { @@ -474,29 +521,6 @@ } assert i == toRemove.size(); } - - // Remove all trivial goto blocks that have a position known to be emitted in their predecessor. - if (!trivialBlocks.isEmpty()) { - List<BasicBlock> blocksToRemove = new ArrayList<>(); - ListIterator<BasicBlock> iterator = code.listIterator(); - // Skip the entry block. - assert code.blocks.size() > 1; - iterator.next(); - BasicBlock nextBlock = iterator.next(); - do { - BasicBlock block = nextBlock; - nextBlock = iterator.hasNext() ? iterator.next() : null; - if (block.isTrivialGoto() - && trivialBlocks.contains(block) - && block.exit().asGoto().getTarget() != block - && !CodeRewriter.isFallthroughBlock(block)) { - BasicBlock target = block.exit().asGoto().getTarget(); - blocksToRemove.add(block); - CodeRewriter.unlinkTrivialGotoBlock(block, target); - } - } while (iterator.hasNext()); - code.removeBlocks(blocksToRemove); - } } // Rewrite ifs with offsets that are too large for the if encoding. The rewriting transforms: @@ -522,8 +546,10 @@ BasicBlock trueTarget = theIf.getTrueTarget(); BasicBlock newBlock = BasicBlock.createGotoBlock( - ir.blocks.size(), theIf.getPosition(), ir.metadata(), trueTarget); + ir.getNextBlockNumber(), theIf.getPosition(), ir.metadata(), trueTarget); theIf.setTrueTarget(newBlock); + newBlock.getMutablePredecessors().add(block); + trueTarget.replacePredecessor(block, newBlock); theIf.invert(); it.add(newBlock); }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java index b46ebad..85ac399 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -703,6 +703,7 @@ new IRCode( appView.options(), method, + source.getCanonicalDebugPositionAtOffset(0), blocks, valueNumberGenerator, basicBlockNumberGenerator,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index fa7a539..0c255da 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1109,7 +1109,7 @@ if (options.canHaveInvokeInterfaceToObjectMethodBug()) { timing.begin("JDK-8272564 fix rewrite"); - CodeRewriter.rewriteJdk8272564Fix(code, appView); + CodeRewriter.rewriteJdk8272564Fix(code, context, appView); timing.end(); }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java index b505cf0..e8d830e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java
@@ -63,7 +63,7 @@ @Override public Position getCanonicalDebugPositionAtOffset(int offset) { - return null; + return Position.syntheticNone(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java index a0f8961..c1e11d4 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
@@ -6,12 +6,10 @@ import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.generateTrackDesugaredAPIWarnings; import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.isAPIConversionSyntheticType; -import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature; import com.android.tools.r8.contexts.CompilationContext.MainThreadContext; import com.android.tools.r8.contexts.CompilationContext.ProcessorContext; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; @@ -21,10 +19,7 @@ import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring; import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer; -import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer; import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification; -import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APICallbackWrapperCfCodeProvider; -import com.android.tools.r8.utils.OptionalBool; import com.android.tools.r8.utils.WorkList; import com.google.common.collect.Sets; import java.util.ArrayList; @@ -71,11 +66,10 @@ trackedCallBackAPIs.add(virtualProgramMethod.getReference()); } ProgramMethod callback = - generateCallbackMethod( - virtualProgramMethod.getDefinition(), - virtualProgramMethod.getHolder(), - eventConsumer, - mainThreadContext); + wrapperSynthesizor + .getConversionCfProvider() + .generateCallbackConversion( + virtualProgramMethod, eventConsumer, mainThreadContext); callbacks.add(callback.getDefinition()); } } @@ -192,33 +186,6 @@ || specification.isEmulatedInterfaceRewrittenType(dexClass.type)); } - private ProgramMethod generateCallbackMethod( - DexEncodedMethod originalMethod, - DexProgramClass clazz, - DesugaredLibraryAPICallbackSynthesizorEventConsumer eventConsumer, - MainThreadContext context) { - DexMethod methodToInstall = - methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView); - CfCode cfCode = - new APICallbackWrapperCfCodeProvider( - appView, - originalMethod.getReference(), - wrapperSynthesizor, - clazz.isInterface(), - eventConsumer, - () -> context.createUniqueContext(clazz)) - .generateCfCode(); - DexEncodedMethod newMethod = wrapperSynthesizor.newSynthesizedMethod(methodToInstall, cfCode); - newMethod.setCode(cfCode, DexEncodedMethod.NO_PARAMETER_INFO); - if (originalMethod.isLibraryMethodOverride().isTrue()) { - newMethod.setLibraryMethodOverride(OptionalBool.TRUE); - } - ProgramMethod callback = new ProgramMethod(clazz, newMethod); - assert eventConsumer != null; - eventConsumer.acceptAPIConversionCallback(callback); - return callback; - } - private void generateTrackingWarnings() { generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ", appView); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java index 55abcef..b4ece95 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
@@ -4,20 +4,10 @@ package com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion; -import com.android.tools.r8.cf.code.CfArrayLoad; -import com.android.tools.r8.cf.code.CfArrayStore; -import com.android.tools.r8.cf.code.CfCheckCast; -import com.android.tools.r8.cf.code.CfConstNumber; import com.android.tools.r8.cf.code.CfInstruction; import com.android.tools.r8.cf.code.CfInvoke; -import com.android.tools.r8.cf.code.CfLoad; -import com.android.tools.r8.cf.code.CfNewArray; -import com.android.tools.r8.cf.code.CfReturn; -import com.android.tools.r8.cf.code.CfStackInstruction; -import com.android.tools.r8.cf.code.CfStackInstruction.Opcode; import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.CfCode; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexClassAndMethod; import com.android.tools.r8.graph.DexItemFactory; @@ -25,22 +15,16 @@ import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.graph.ProgramMethod; -import com.android.tools.r8.ir.code.MemberType; -import com.android.tools.r8.ir.code.ValueType; import com.android.tools.r8.ir.desugar.CfInstructionDesugaring; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection; import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.FreshLocalProvider; import com.android.tools.r8.ir.desugar.LocalStackAllocator; -import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer; -import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.StringDiagnostic; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Set; @@ -239,94 +223,6 @@ return vivifiedType; } - private static DexType invalidType( - DexMethod invokedMethod, - DexMethod returnConversion, - DexMethod[] parameterConversions, - AppView<?> appView) { - DexMethod convertedMethod = - methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView); - if (invokedMethod.getReturnType() != convertedMethod.getReturnType() - && returnConversion == null) { - return invokedMethod.getReturnType(); - } - for (int i = 0; i < invokedMethod.getArity(); i++) { - if (invokedMethod.getParameter(i) != convertedMethod.getParameter(i) - && parameterConversions[i] == null) { - return invokedMethod.getParameter(i); - } - } - return null; - } - - public static DexMethod getConvertedAPI( - DexMethod invokedMethod, - DexMethod returnConversion, - DexMethod[] parameterConversions, - AppView<?> appView) { - DexType newReturnType = - returnConversion != null ? returnConversion.getParameter(0) : invokedMethod.getReturnType(); - DexType[] newParameterTypes = new DexType[parameterConversions.length]; - for (int i = 0; i < parameterConversions.length; i++) { - newParameterTypes[i] = - parameterConversions[i] != null - ? parameterConversions[i].getReturnType() - : invokedMethod.getParameter(i); - } - DexMethod convertedAPI = - appView - .dexItemFactory() - .createMethod( - invokedMethod.holder, - appView.dexItemFactory().createProto(newReturnType, newParameterTypes), - invokedMethod.name); - assert convertedAPI - == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView) - || invalidType(invokedMethod, returnConversion, parameterConversions, appView) != null; - return convertedAPI; - } - - private DexMethod computeReturnConversion( - DexMethod invokedMethod, - DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, - ProgramMethod context, - MethodProcessingContext methodProcessingContext) { - DexType returnType = invokedMethod.proto.returnType; - if (wrapperSynthesizor.shouldConvert(returnType, invokedMethod, context)) { - DexType newReturnType = DesugaredLibraryAPIConverter.vivifiedTypeFor(returnType, appView); - return wrapperSynthesizor.ensureConversionMethod( - returnType, - newReturnType, - returnType, - eventConsumer, - methodProcessingContext::createUniqueContext); - } - return null; - } - - private DexMethod[] computeParameterConversions( - DexMethod invokedMethod, - DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, - ProgramMethod context, - MethodProcessingContext methodProcessingContext) { - DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()]; - DexType[] parameters = invokedMethod.proto.parameters.values; - for (int i = 0; i < parameters.length; i++) { - DexType argType = parameters[i]; - if (wrapperSynthesizor.shouldConvert(argType, invokedMethod, context)) { - DexType argVivifiedType = vivifiedTypeFor(argType, appView); - parameterConversions[i] = - wrapperSynthesizor.ensureConversionMethod( - argType, - argType, - argVivifiedType, - eventConsumer, - methodProcessingContext::createUniqueContext); - } - } - return parameterConversions; - } - private Collection<CfInstruction> rewriteLibraryInvoke( CfInvoke invoke, MethodProcessingContext methodProcessingContext, @@ -339,12 +235,18 @@ } if (shouldOutlineAPIConversion(invoke, context)) { DexMethod outlinedAPIConversion = - createOutlinedAPIConversion(invoke, methodProcessingContext, eventConsumer, context); + wrapperSynthesizor + .getConversionCfProvider() + .generateOutlinedAPIConversion( + invoke, eventConsumer, context, methodProcessingContext) + .getReference(); return Collections.singletonList( new CfInvoke(Opcodes.INVOKESTATIC, outlinedAPIConversion, false)); } - return rewriteLibraryInvokeToInlineAPIConversion( - invoke, methodProcessingContext, localStackAllocator, eventConsumer, context); + return wrapperSynthesizor + .getConversionCfProvider() + .generateInlinedAPIConversion( + invoke, methodProcessingContext, localStackAllocator, eventConsumer, context); } // If the option is set, we try to outline API conversions as much as possible to reduce the @@ -363,195 +265,6 @@ return methodForDesugaring.getAccessFlags().isPublic(); } - private Collection<CfInstruction> rewriteLibraryInvokeToInlineAPIConversion( - CfInvoke invoke, - MethodProcessingContext methodProcessingContext, - LocalStackAllocator localStackAllocator, - CfInstructionDesugaringEventConsumer eventConsumer, - ProgramMethod context) { - DexMethod invokedMethod = invoke.getMethod(); - DexMethod returnConversion = - computeReturnConversion(invokedMethod, eventConsumer, context, methodProcessingContext); - DexMethod[] parameterConversions = - computeParameterConversions(invokedMethod, eventConsumer, context, methodProcessingContext); - // If only the last 2 parameters require conversion, we do everything inlined. - // If other parameters require conversion, we outline the parameter conversion but keep the API - // call inlined. - // The returned value is always converted inlined. - boolean requireOutlinedParameterConversion = false; - for (int i = 0; i < parameterConversions.length - 2; i++) { - requireOutlinedParameterConversion |= parameterConversions[i] != null; - } - - ArrayList<CfInstruction> cfInstructions = new ArrayList<>(); - if (requireOutlinedParameterConversion) { - addOutlineParameterConversionInstructions( - parameterConversions, - cfInstructions, - methodProcessingContext, - invokedMethod, - localStackAllocator, - eventConsumer); - } else { - addInlineParameterConversionInstructions(parameterConversions, cfInstructions); - } - - DexMethod convertedMethod = - getConvertedAPI(invokedMethod, returnConversion, parameterConversions, appView); - cfInstructions.add(new CfInvoke(invoke.getOpcode(), convertedMethod, invoke.isInterface())); - - if (returnConversion != null) { - cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false)); - } - - return cfInstructions; - } - - // The parameters are converted and returned in an array of converted parameters. The parameter - // array then needs to be unwrapped at the call site. - private void addOutlineParameterConversionInstructions( - DexMethod[] parameterConversions, - ArrayList<CfInstruction> cfInstructions, - MethodProcessingContext methodProcessingContext, - DexMethod invokedMethod, - LocalStackAllocator localStackAllocator, - CfInstructionDesugaringEventConsumer eventConsumer) { - localStackAllocator.allocateLocalStack(4); - DexProto newProto = - appView - .dexItemFactory() - .createProto( - appView.dexItemFactory().objectArrayType, invokedMethod.getParameters().values); - ProgramMethod parameterConversion = - appView - .getSyntheticItems() - .createMethod( - kinds -> kinds.API_CONVERSION_PARAMETERS, - methodProcessingContext.createUniqueContext(), - appView, - builder -> - builder - .setProto(newProto) - .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) - // Will be traced by the enqueuer. - .disableAndroidApiLevelCheck() - .setCode( - methodSignature -> - computeParameterConversionCfCode( - methodSignature.holder, invokedMethod, parameterConversions))); - eventConsumer.acceptAPIConversion(parameterConversion); - cfInstructions.add( - new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion.getReference(), false)); - for (int i = 0; i < parameterConversions.length; i++) { - cfInstructions.add(new CfStackInstruction(Opcode.Dup)); - cfInstructions.add(new CfConstNumber(i, ValueType.INT)); - DexType parameterType = - parameterConversions[i] != null - ? parameterConversions[i].getReturnType() - : invokedMethod.getParameter(i); - cfInstructions.add(new CfArrayLoad(MemberType.OBJECT)); - if (parameterType.isPrimitiveType()) { - cfInstructions.add(new CfCheckCast(factory.getBoxedForPrimitiveType(parameterType))); - DexMethod method = appView.dexItemFactory().getUnboxPrimitiveMethod(parameterType); - cfInstructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false)); - } else { - cfInstructions.add(new CfCheckCast(parameterType)); - } - cfInstructions.add(new CfStackInstruction(Opcode.Swap)); - } - cfInstructions.add(new CfStackInstruction(Opcode.Pop)); - } - - private CfCode computeParameterConversionCfCode( - DexType holder, DexMethod invokedMethod, DexMethod[] parameterConversions) { - ArrayList<CfInstruction> cfInstructions = new ArrayList<>(); - cfInstructions.add(new CfConstNumber(parameterConversions.length, ValueType.INT)); - cfInstructions.add(new CfNewArray(factory.objectArrayType)); - int stackIndex = 0; - for (int i = 0; i < invokedMethod.getArity(); i++) { - cfInstructions.add(new CfStackInstruction(Opcode.Dup)); - cfInstructions.add(new CfConstNumber(i, ValueType.INT)); - DexType param = invokedMethod.getParameter(i); - cfInstructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex)); - if (parameterConversions[i] != null) { - cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false)); - } - if (param.isPrimitiveType()) { - DexMethod method = appView.dexItemFactory().getBoxPrimitiveMethod(param); - cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false)); - } - cfInstructions.add(new CfArrayStore(MemberType.OBJECT)); - if (param == appView.dexItemFactory().longType - || param == appView.dexItemFactory().doubleType) { - stackIndex++; - } - stackIndex++; - } - cfInstructions.add(new CfReturn(ValueType.OBJECT)); - return new CfCode( - holder, - invokedMethod.getParameters().size() + 4, - invokedMethod.getParameters().size(), - cfInstructions); - } - - private void addInlineParameterConversionInstructions( - DexMethod[] parameterConversions, ArrayList<CfInstruction> cfInstructions) { - if (parameterConversions.length > 0 - && parameterConversions[parameterConversions.length - 1] != null) { - cfInstructions.add( - new CfInvoke( - Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 1], false)); - } - if (parameterConversions.length > 1 - && parameterConversions[parameterConversions.length - 2] != null) { - cfInstructions.add(new CfStackInstruction(Opcode.Swap)); - cfInstructions.add( - new CfInvoke( - Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 2], false)); - cfInstructions.add(new CfStackInstruction(Opcode.Swap)); - } - } - - private DexMethod createOutlinedAPIConversion( - CfInvoke invoke, - MethodProcessingContext methodProcessingContext, - CfInstructionDesugaringEventConsumer eventConsumer, - ProgramMethod context) { - DexMethod invokedMethod = invoke.getMethod(); - DexProto newProto = - invoke.isInvokeStatic() - ? invokedMethod.proto - : factory.prependTypeToProto(invokedMethod.getHolderType(), invokedMethod.getProto()); - DexMethod returnConversion = - computeReturnConversion(invokedMethod, eventConsumer, context, methodProcessingContext); - DexMethod[] parameterConversions = - computeParameterConversions(invokedMethod, eventConsumer, context, methodProcessingContext); - ProgramMethod outline = - appView - .getSyntheticItems() - .createMethod( - kinds -> kinds.API_CONVERSION, - methodProcessingContext.createUniqueContext(), - appView, - builder -> - builder - .setProto(newProto) - .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) - // Will be traced by the enqueuer. - .disableAndroidApiLevelCheck() - .setCode( - methodSignature -> - new APIConversionCfCodeProvider( - appView, - methodSignature.holder, - invoke, - returnConversion, - parameterConversions) - .generateCfCode())); - eventConsumer.acceptAPIConversion(outline); - return outline.getReference(); - } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java new file mode 100644 index 0000000..c458830 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -0,0 +1,551 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion; + +import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature; + +import com.android.tools.r8.cf.code.CfArrayLoad; +import com.android.tools.r8.cf.code.CfArrayStore; +import com.android.tools.r8.cf.code.CfCheckCast; +import com.android.tools.r8.cf.code.CfConstNumber; +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfInvoke; +import com.android.tools.r8.cf.code.CfLoad; +import com.android.tools.r8.cf.code.CfNewArray; +import com.android.tools.r8.cf.code.CfReturn; +import com.android.tools.r8.cf.code.CfStackInstruction; +import com.android.tools.r8.cf.code.CfStackInstruction.Opcode; +import com.android.tools.r8.contexts.CompilationContext.MainThreadContext; +import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext; +import com.android.tools.r8.contexts.CompilationContext.UniqueContext; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexProto; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.MethodAccessFlags; +import com.android.tools.r8.graph.ProgramMethod; +import com.android.tools.r8.ir.code.MemberType; +import com.android.tools.r8.ir.code.ValueType; +import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer; +import com.android.tools.r8.ir.desugar.LocalStackAllocator; +import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer; +import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer; +import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer; +import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer; +import com.android.tools.r8.ir.synthetic.apiconverter.APIConversionCfCodeProvider; +import com.android.tools.r8.utils.OptionalBool; +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Function; +import java.util.function.Supplier; +import org.objectweb.asm.Opcodes; + +public class DesugaredLibraryConversionCfProvider { + + private final AppView<?> appView; + private final DexItemFactory factory; + private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizer; + + public DesugaredLibraryConversionCfProvider( + AppView<?> appView, DesugaredLibraryWrapperSynthesizer wrapperSynthesizer) { + this.appView = appView; + this.factory = appView.dexItemFactory(); + this.wrapperSynthesizer = wrapperSynthesizer; + } + + public DexEncodedMethod generateWrapperConversionWithoutCode( + DexMethod method, DexField wrapperField) { + DexMethod methodToInstall = + methodWithVivifiedTypeInSignature(method, wrapperField.getHolderType(), appView); + return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, null); + } + + public DexEncodedMethod generateWrapperConversion( + DexMethod method, + DexField wrapperField, + DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, + Supplier<UniqueContext> contextSupplier) { + DexClass holderClass = appView.definitionFor(method.getHolderType()); + assert holderClass != null || appView.options().isDesugaredLibraryCompilation(); + boolean isInterface = holderClass == null || holderClass.isInterface(); + ProgramMethod context = resolveContext(method, isInterface); + DexMethod returnConversion = + computeReturnConversion(method, true, eventConsumer, context, contextSupplier); + DexMethod[] parameterConversions = + computeParameterConversions(method, false, eventConsumer, context, contextSupplier); + DexMethod methodToInstall = + convertedMethod( + method, false, returnConversion, parameterConversions, wrapperField.getHolderType()); + CfCode cfCode = + new APIConversionCfCodeProvider( + appView, + wrapperField.getHolderType(), + method, + isInterface, + returnConversion, + parameterConversions, + wrapperField) + .generateCfCode(); + return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode); + } + + public DexEncodedMethod generateVivifiedWrapperConversionWithoutCode( + DexMethod method, DexField wrapperField) { + DexMethod methodToInstall = + factory.createMethod(wrapperField.getHolderType(), method.proto, method.name); + return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, null); + } + + public DexEncodedMethod generateVivifiedWrapperConversion( + DexMethod method, + DexField wrapperField, + DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, + Supplier<UniqueContext> contextSupplier) { + DexMethod methodToInstall = + factory.createMethod(wrapperField.getHolderType(), method.proto, method.name); + DexClass holderClass = appView.definitionFor(method.getHolderType()); + boolean isInterface; + if (holderClass == null) { + assert appView + .options() + .machineDesugaredLibrarySpecification + .isEmulatedInterfaceRewrittenType(method.getHolderType()); + isInterface = true; + } else { + isInterface = holderClass.isInterface(); + } + ProgramMethod context = resolveContext(method, isInterface); + DexMethod returnConversion = + computeReturnConversion(method, false, eventConsumer, context, contextSupplier); + DexMethod[] parameterConversions = + computeParameterConversions(method, true, eventConsumer, context, contextSupplier); + DexMethod forwardMethod = + convertedMethod( + method, true, returnConversion, parameterConversions, wrapperField.getType()); + CfCode cfCode = + new APIConversionCfCodeProvider( + appView, + wrapperField.getHolderType(), + forwardMethod, + isInterface, + returnConversion, + parameterConversions, + wrapperField) + .generateCfCode(); + return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode); + } + + public ProgramMethod generateCallbackConversion( + ProgramMethod method, + DesugaredLibraryAPICallbackSynthesizorEventConsumer eventConsumer, + MainThreadContext context) { + DexProgramClass clazz = method.getHolder(); + DexMethod returnConversion = + computeReturnConversion( + method.getReference(), + true, + eventConsumer, + method, + () -> context.createUniqueContext(clazz)); + DexMethod[] parameterConversions = + computeParameterConversions( + method.getReference(), + false, + eventConsumer, + method, + () -> context.createUniqueContext(clazz)); + DexMethod methodToInstall = + convertedMethod(method.getReference(), false, returnConversion, parameterConversions); + CfCode cfCode = + new APIConversionCfCodeProvider( + appView, + method.getHolderType(), + method.getReference(), + clazz.isInterface(), + returnConversion, + parameterConversions) + .generateCfCode(); + DexEncodedMethod newMethod = wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode); + newMethod.setCode(cfCode, DexEncodedMethod.NO_PARAMETER_INFO); + if (method.getDefinition().isLibraryMethodOverride().isTrue()) { + newMethod.setLibraryMethodOverride(OptionalBool.TRUE); + } + ProgramMethod callback = new ProgramMethod(clazz, newMethod); + assert eventConsumer != null; + eventConsumer.acceptAPIConversionCallback(callback); + return callback; + } + + public ProgramMethod generateOutlinedAPIConversion( + CfInvoke invoke, + DesugaredLibraryAPIConverterEventConsumer eventConsumer, + ProgramMethod context, + MethodProcessingContext methodProcessingContext) { + DexMethod method = invoke.getMethod(); + DexProto newProto = + invoke.isInvokeStatic() + ? method.proto + : factory.prependTypeToProto(method.getHolderType(), method.getProto()); + DexMethod returnConversion = + computeReturnConversion( + method, false, eventConsumer, context, methodProcessingContext::createUniqueContext); + DexMethod[] parameterConversions = + computeParameterConversions( + method, true, eventConsumer, context, methodProcessingContext::createUniqueContext); + ProgramMethod outline = + appView + .getSyntheticItems() + .createMethod( + kinds -> kinds.API_CONVERSION, + methodProcessingContext.createUniqueContext(), + appView, + builder -> + builder + .setProto(newProto) + .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) + // Will be traced by the enqueuer. + .disableAndroidApiLevelCheck() + .setCode( + methodSignature -> + new APIConversionCfCodeProvider( + appView, + methodSignature.holder, + convertedMethod( + method, true, returnConversion, parameterConversions), + invoke.isInterface(), + returnConversion, + parameterConversions, + invoke.getOpcode()) + .generateCfCode())); + eventConsumer.acceptAPIConversion(outline); + return outline; + } + + public Collection<CfInstruction> generateInlinedAPIConversion( + CfInvoke invoke, + MethodProcessingContext methodProcessingContext, + LocalStackAllocator localStackAllocator, + CfInstructionDesugaringEventConsumer eventConsumer, + ProgramMethod context) { + + DexMethod invokedMethod = invoke.getMethod(); + DexMethod returnConversion = + computeReturnConversion( + invokedMethod, + false, + eventConsumer, + context, + methodProcessingContext::createUniqueContext); + DexMethod[] parameterConversions = + computeParameterConversions( + invokedMethod, + true, + eventConsumer, + context, + methodProcessingContext::createUniqueContext); + + // If only the last 2 parameters require conversion, we do everything inlined. + // If other parameters require conversion, we outline the parameter conversion but keep the API + // call inlined. + // The returned value is always converted inlined. + boolean requireOutlinedParameterConversion = false; + for (int i = 0; i < parameterConversions.length - 2; i++) { + requireOutlinedParameterConversion |= parameterConversions[i] != null; + } + + ArrayList<CfInstruction> cfInstructions = new ArrayList<>(); + if (requireOutlinedParameterConversion) { + addOutlineParameterConversionInstructions( + parameterConversions, + cfInstructions, + methodProcessingContext, + invokedMethod, + localStackAllocator, + eventConsumer); + } else { + addInlineParameterConversionInstructions(parameterConversions, cfInstructions); + } + + DexMethod convertedMethod = + convertedMethod(invokedMethod, true, returnConversion, parameterConversions); + cfInstructions.add(new CfInvoke(invoke.getOpcode(), convertedMethod, invoke.isInterface())); + + if (returnConversion != null) { + cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false)); + } + + return cfInstructions; + } + + // The parameters are converted and returned in an array of converted parameters. The parameter + // array then needs to be unwrapped at the call site. + private void addOutlineParameterConversionInstructions( + DexMethod[] parameterConversions, + ArrayList<CfInstruction> cfInstructions, + MethodProcessingContext methodProcessingContext, + DexMethod invokedMethod, + LocalStackAllocator localStackAllocator, + CfInstructionDesugaringEventConsumer eventConsumer) { + localStackAllocator.allocateLocalStack(4); + DexProto newProto = + appView + .dexItemFactory() + .createProto( + appView.dexItemFactory().objectArrayType, invokedMethod.getParameters().values); + ProgramMethod parameterConversion = + appView + .getSyntheticItems() + .createMethod( + kinds -> kinds.API_CONVERSION_PARAMETERS, + methodProcessingContext.createUniqueContext(), + appView, + builder -> + builder + .setProto(newProto) + .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic()) + // Will be traced by the enqueuer. + .disableAndroidApiLevelCheck() + .setCode( + methodSignature -> + computeParameterConversionCfCode( + methodSignature.holder, invokedMethod, parameterConversions))); + eventConsumer.acceptAPIConversion(parameterConversion); + cfInstructions.add( + new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion.getReference(), false)); + for (int i = 0; i < parameterConversions.length; i++) { + cfInstructions.add(new CfStackInstruction(Opcode.Dup)); + cfInstructions.add(new CfConstNumber(i, ValueType.INT)); + DexType parameterType = + parameterConversions[i] != null + ? parameterConversions[i].getReturnType() + : invokedMethod.getParameter(i); + cfInstructions.add(new CfArrayLoad(MemberType.OBJECT)); + if (parameterType.isPrimitiveType()) { + cfInstructions.add(new CfCheckCast(factory.getBoxedForPrimitiveType(parameterType))); + DexMethod method = appView.dexItemFactory().getUnboxPrimitiveMethod(parameterType); + cfInstructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false)); + } else { + cfInstructions.add(new CfCheckCast(parameterType)); + } + cfInstructions.add(new CfStackInstruction(Opcode.Swap)); + } + cfInstructions.add(new CfStackInstruction(Opcode.Pop)); + } + + private CfCode computeParameterConversionCfCode( + DexType holder, DexMethod invokedMethod, DexMethod[] parameterConversions) { + ArrayList<CfInstruction> cfInstructions = new ArrayList<>(); + cfInstructions.add(new CfConstNumber(parameterConversions.length, ValueType.INT)); + cfInstructions.add(new CfNewArray(factory.objectArrayType)); + int stackIndex = 0; + for (int i = 0; i < invokedMethod.getArity(); i++) { + cfInstructions.add(new CfStackInstruction(Opcode.Dup)); + cfInstructions.add(new CfConstNumber(i, ValueType.INT)); + DexType param = invokedMethod.getParameter(i); + cfInstructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex)); + if (parameterConversions[i] != null) { + cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false)); + } + if (param.isPrimitiveType()) { + DexMethod method = appView.dexItemFactory().getBoxPrimitiveMethod(param); + cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false)); + } + cfInstructions.add(new CfArrayStore(MemberType.OBJECT)); + if (param == appView.dexItemFactory().longType + || param == appView.dexItemFactory().doubleType) { + stackIndex++; + } + stackIndex++; + } + cfInstructions.add(new CfReturn(ValueType.OBJECT)); + return new CfCode( + holder, + invokedMethod.getParameters().size() + 4, + invokedMethod.getParameters().size(), + cfInstructions); + } + + private void addInlineParameterConversionInstructions( + DexMethod[] parameterConversions, ArrayList<CfInstruction> cfInstructions) { + if (parameterConversions.length > 0 + && parameterConversions[parameterConversions.length - 1] != null) { + cfInstructions.add( + new CfInvoke( + Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 1], false)); + } + if (parameterConversions.length > 1 + && parameterConversions[parameterConversions.length - 2] != null) { + cfInstructions.add(new CfStackInstruction(Opcode.Swap)); + cfInstructions.add( + new CfInvoke( + Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 2], false)); + cfInstructions.add(new CfStackInstruction(Opcode.Swap)); + } + } + + private ProgramMethod resolveContext(DexMethod method, boolean isInterface) { + // The context is provided to improve the error message with origin and position. If the method + // is missing from the input, the context is null but the code runs correctly. + return appView + .appInfoForDesugaring() + .resolveMethodLegacy(method, isInterface) + .getResolvedProgramMethod(); + } + + private DexMethod computeReturnConversion( + DexMethod invokedMethod, + boolean destIsVivified, + DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, + ProgramMethod context, + Supplier<UniqueContext> contextSupplier) { + return internalComputeReturnConversion( + invokedMethod, + returnType -> + wrapperSynthesizer.ensureConversionMethod( + returnType, destIsVivified, eventConsumer, contextSupplier), + context); + } + + private DexMethod computeReturnConversion( + DexMethod invokedMethod, + boolean destIsVivified, + DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, + ProgramMethod context, + Supplier<UniqueContext> contextSupplier) { + return internalComputeReturnConversion( + invokedMethod, + returnType -> + wrapperSynthesizer.getExistingProgramConversionMethod( + returnType, destIsVivified, eventConsumer, contextSupplier), + context); + } + + private DexMethod internalComputeReturnConversion( + DexMethod invokedMethod, Function<DexType, DexMethod> methodSupplier, ProgramMethod context) { + DexType returnType = invokedMethod.proto.returnType; + if (wrapperSynthesizer.shouldConvert(returnType, invokedMethod, context)) { + return methodSupplier.apply(returnType); + } + return null; + } + + private DexMethod[] computeParameterConversions( + DexMethod invokedMethod, + boolean destIsVivified, + DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, + ProgramMethod context, + Supplier<UniqueContext> contextSupplier) { + return internalComputeParameterConversions( + invokedMethod, + wrapperSynthesizer, + argType -> + wrapperSynthesizer.ensureConversionMethod( + argType, destIsVivified, eventConsumer, contextSupplier), + context); + } + + private DexMethod[] computeParameterConversions( + DexMethod invokedMethod, + boolean destIsVivified, + DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, + ProgramMethod context, + Supplier<UniqueContext> contextSupplier) { + return internalComputeParameterConversions( + invokedMethod, + wrapperSynthesizer, + argType -> + wrapperSynthesizer.getExistingProgramConversionMethod( + argType, destIsVivified, eventConsumer, contextSupplier), + context); + } + + private DexMethod[] internalComputeParameterConversions( + DexMethod invokedMethod, + DesugaredLibraryWrapperSynthesizer wrapperSynthesizor, + Function<DexType, DexMethod> methodSupplier, + ProgramMethod context) { + DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()]; + DexType[] parameters = invokedMethod.proto.parameters.values; + for (int i = 0; i < parameters.length; i++) { + DexType argType = parameters[i]; + if (wrapperSynthesizor.shouldConvert(argType, invokedMethod, context)) { + parameterConversions[i] = methodSupplier.apply(argType); + } + } + return parameterConversions; + } + + private DexMethod convertedMethod( + DexMethod method, + boolean parameterDestIsVivified, + DexMethod returnConversion, + DexMethod[] parameterConversions) { + return convertedMethod( + method, + parameterDestIsVivified, + returnConversion, + parameterConversions, + method.getHolderType()); + } + + private DexMethod convertedMethod( + DexMethod method, + boolean parameterDestIsVivified, + DexMethod returnConversion, + DexMethod[] parameterConversions, + DexType newHolder) { + DexType newReturnType = + returnConversion != null + ? parameterDestIsVivified + ? returnConversion.getParameter(0) + : returnConversion.getReturnType() + : method.getReturnType(); + DexType[] newParameterTypes = new DexType[parameterConversions.length]; + for (int i = 0; i < parameterConversions.length; i++) { + newParameterTypes[i] = + parameterConversions[i] != null + ? parameterDestIsVivified + ? parameterConversions[i].getReturnType() + : parameterConversions[i].getParameter(0) + : method.getParameter(i); + } + DexMethod convertedAPI = + appView + .dexItemFactory() + .createMethod( + newHolder, + appView.dexItemFactory().createProto(newReturnType, newParameterTypes), + method.name); + assert convertedAPI == methodWithVivifiedTypeInSignature(method, newHolder, appView) + || invalidType(method, returnConversion, parameterConversions, appView) != null; + return convertedAPI; + } + + private static DexType invalidType( + DexMethod invokedMethod, + DexMethod returnConversion, + DexMethod[] parameterConversions, + AppView<?> appView) { + DexMethod convertedMethod = + methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView); + if (invokedMethod.getReturnType() != convertedMethod.getReturnType() + && returnConversion == null) { + return invokedMethod.getReturnType(); + } + for (int i = 0; i < invokedMethod.getArity(); i++) { + if (invokedMethod.getParameter(i) != convertedMethod.getParameter(i) + && parameterConversions[i] == null) { + return invokedMethod.getParameter(i); + } + } + return null; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java index d69a158..a72ee2f 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
@@ -17,7 +17,7 @@ import com.android.tools.r8.graph.MethodAccessFlags; import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer; import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer; -import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.EnumConversionCfCodeProvider; +import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.EnumConversionCfCodeProvider; import com.android.tools.r8.synthesis.SyntheticClasspathClassBuilder; import com.android.tools.r8.synthesis.SyntheticMethodBuilder; import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java index 47f48d4..8507816 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -31,11 +31,9 @@ import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer; import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.CustomConversionDescriptor; import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification; -import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider; -import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider; -import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider; -import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider; -import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.ArrayConversionCfCodeProvider; +import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider; +import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.ArrayConversionCfCodeProvider; +import com.android.tools.r8.ir.synthetic.apiconverter.WrapperConstructorCfCodeProvider; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.position.MethodPosition; import com.android.tools.r8.position.Position; @@ -49,7 +47,7 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.function.Supplier; // I am responsible for the generation of wrappers used to call library APIs when desugaring @@ -100,11 +98,17 @@ private final AppView<?> appView; private final DexItemFactory factory; private final DesugaredLibraryEnumConversionSynthesizer enumConverter; + private final DesugaredLibraryConversionCfProvider conversionCfProvider; public DesugaredLibraryWrapperSynthesizer(AppView<?> appView) { this.appView = appView; this.factory = appView.dexItemFactory(); this.enumConverter = new DesugaredLibraryEnumConversionSynthesizer(appView); + this.conversionCfProvider = new DesugaredLibraryConversionCfProvider(appView, this); + } + + public DesugaredLibraryConversionCfProvider getConversionCfProvider() { + return conversionCfProvider; } public boolean isSyntheticWrapper(DexType type) { @@ -132,10 +136,11 @@ public DexMethod ensureConversionMethod( DexType type, - DexType srcType, - DexType destType, + boolean destIsVivified, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, Supplier<UniqueContext> contextSupplier) { + DexType srcType = destIsVivified ? type : vivifiedTypeFor(type); + DexType destType = destIsVivified ? vivifiedTypeFor(type) : type; if (type.isArrayType()) { return ensureArrayConversionMethod(type, srcType, destType, eventConsumer, contextSupplier); } @@ -166,11 +171,7 @@ Supplier<UniqueContext> contextSupplier) { DexMethod conversion = ensureConversionMethod( - type.toDimensionMinusOneType(factory), - srcType.toDimensionMinusOneType(factory), - destType.toDimensionMinusOneType(factory), - eventConsumer, - contextSupplier); + type.toDimensionMinusOneType(factory), srcType == type, eventConsumer, contextSupplier); return ensureArrayConversionMethod( srcType, destType, eventConsumer, contextSupplier, conversion); } @@ -183,11 +184,7 @@ Supplier<UniqueContext> contextSupplier) { DexMethod conversion = getExistingProgramConversionMethod( - type.toDimensionMinusOneType(factory), - srcType.toDimensionMinusOneType(factory), - destType.toDimensionMinusOneType(factory), - eventConsumer, - contextSupplier); + type.toDimensionMinusOneType(factory), srcType == type, eventConsumer, contextSupplier); return ensureArrayConversionMethod( srcType, destType, eventConsumer, contextSupplier, conversion); } @@ -224,10 +221,11 @@ public DexMethod getExistingProgramConversionMethod( DexType type, - DexType srcType, - DexType destType, + boolean destIsVivified, DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, Supplier<UniqueContext> contextSupplier) { + DexType srcType = destIsVivified ? type : vivifiedTypeFor(type); + DexType destType = destIsVivified ? vivifiedTypeFor(type) : type; if (type.isArrayType()) { return ensureArrayConversionMethodFromExistingBaseConversion( type, srcType, destType, eventConsumer, contextSupplier); @@ -356,11 +354,8 @@ type, classpathOrLibraryContext, eventConsumer, - wrapperField -> - synthesizeVirtualMethodsForTypeWrapper( - methods, - wrapperField, - DesugaredLibraryWrapperSynthesizer::codeForClasspathMethod)); + methods, + conversionCfProvider::generateWrapperConversionWithoutCode); DexClass vivifiedWrapper = ensureClasspathWrapper( kinds -> kinds.VIVIFIED_WRAPPER, @@ -368,11 +363,8 @@ vivifiedType, classpathOrLibraryContext, eventConsumer, - wrapperField -> - synthesizeVirtualMethodsForVivifiedTypeWrapper( - methods, - wrapperField, - DesugaredLibraryWrapperSynthesizer::codeForClasspathMethod)); + methods, + conversionCfProvider::generateVivifiedWrapperConversionWithoutCode); return new WrapperConversions( getConversion(wrapper, vivifiedType, type), getConversion(vivifiedWrapper, type, vivifiedType)); @@ -438,7 +430,8 @@ DexType wrappedType, ClasspathOrLibraryClass classpathOrLibraryContext, DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, - Function<DexEncodedField, Collection<DexEncodedMethod>> virtualMethodProvider) { + Iterable<DexMethod> methods, + BiFunction<DexMethod, DexField, DexEncodedMethod> methodGenerator) { assert eventConsumer != null; return appView .getSyntheticItems() @@ -454,7 +447,8 @@ methodBuilder -> buildConversionMethod( methodBuilder, factory.createProto(wrappingType, wrappedType), null)); - builder.setVirtualMethods(virtualMethodProvider.apply(wrapperField)); + builder.setVirtualMethods( + synthesizeVirtualMethodsForWrapper(methods, wrapperField, methodGenerator)); }, eventConsumer::acceptWrapperClasspathClass); } @@ -487,7 +481,7 @@ private CfCode computeProgramConversionMethodCode( DexField wrapperField, DexField reverseWrapperField, DexClass context) { assert context.isProgramClass(); - return new APIConverterWrapperConversionCfCodeProvider( + return new NullableConversionCfCodeProvider.WrapperConversionCfCodeProvider( appView, reverseWrapperField, wrapperField) .generateCfCode(); } @@ -537,80 +531,24 @@ .disableAndroidApiLevelCheck() .setCode( codeSynthesizor -> - new APIConverterConstructorCfCodeProvider( + new WrapperConstructorCfCodeProvider( appView, wrappedValueField.getReference(), superType) .generateCfCode()); } - private static CfCode codeForClasspathMethod(DexMethod ignore) { - return null; - } - - private Collection<DexEncodedMethod> synthesizeVirtualMethodsForVivifiedTypeWrapper( - Iterable<DexMethod> allImplementedMethods, - DexEncodedField wrapperField, - Function<DexMethod, CfCode> cfCodeProvider) { - List<DexEncodedMethod> generatedMethods = new ArrayList<>(); - for (DexMethod method : allImplementedMethods) { - DexMethod methodToInstall = - factory.createMethod(wrapperField.getHolderType(), method.proto, method.name); - CfCode cfCode = cfCodeProvider.apply(method); - DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode); - generatedMethods.add(newDexEncodedMethod); - } - return generatedMethods; - } - - private CfCode synthesizeCfCodeForVivifiedTypeWrapper( - DexMethod method, - DexField wrapperField, - DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, - Supplier<UniqueContext> contextSupplier) { - DexClass holderClass = appView.definitionFor(method.getHolderType()); - boolean isInterface; - if (holderClass == null) { - assert appView - .options() - .machineDesugaredLibrarySpecification - .isEmulatedInterfaceRewrittenType(method.getHolderType()); - isInterface = true; - } else { - isInterface = holderClass.isInterface(); - } - return new APIConverterVivifiedWrapperCfCodeProvider( - appView, method, wrapperField, this, isInterface, eventConsumer, contextSupplier) - .generateCfCode(); - } - - private Collection<DexEncodedMethod> synthesizeVirtualMethodsForTypeWrapper( + private Collection<DexEncodedMethod> synthesizeVirtualMethodsForWrapper( Iterable<DexMethod> dexMethods, DexEncodedField wrapperField, - Function<DexMethod, CfCode> cfCodeProvider) { + BiFunction<DexMethod, DexField, DexEncodedMethod> methodGenerator) { List<DexEncodedMethod> generatedMethods = new ArrayList<>(); for (DexMethod method : dexMethods) { - DexMethod methodToInstall = - DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature( - method, wrapperField.getHolderType(), appView); - CfCode cfCode = cfCodeProvider.apply(method); - DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode); + DexEncodedMethod newDexEncodedMethod = + methodGenerator.apply(method, wrapperField.getReference()); generatedMethods.add(newDexEncodedMethod); } return generatedMethods; } - private CfCode synthesizeCfCodeForTypeWrapper( - DexMethod method, - DexField wrapperField, - DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, - Supplier<UniqueContext> contextSupplier) { - DexClass holderClass = appView.definitionFor(method.getHolderType()); - assert holderClass != null || appView.options().isDesugaredLibraryCompilation(); - boolean isInterface = holderClass == null || holderClass.isInterface(); - return new APIConverterWrapperCfCodeProvider( - appView, method, wrapperField, this, isInterface, eventConsumer, contextSupplier) - .generateCfCode(); - } - DexEncodedMethod newSynthesizedMethod(DexMethod methodToInstall, Code code) { MethodAccessFlags newFlags = MethodAccessFlags.fromSharedAccessFlags( @@ -722,27 +660,27 @@ DexProgramClass wrapper = getExistingProgramWrapper(context, kinds -> kinds.WRAPPER); DexEncodedField wrapperField = getWrapperUniqueEncodedField(wrapper); wrapper.addVirtualMethods( - synthesizeVirtualMethodsForTypeWrapper( + synthesizeVirtualMethodsForWrapper( methods, wrapperField, - m -> - synthesizeCfCodeForTypeWrapper( - m, - wrapperField.getReference(), + (method, field) -> + conversionCfProvider.generateWrapperConversion( + method, + field, eventConsumer, () -> processingContext.createUniqueContext(wrapper)))); DexProgramClass vivifiedWrapper = getExistingProgramWrapper(context, kinds -> kinds.VIVIFIED_WRAPPER); DexEncodedField vivifiedWrapperField = getWrapperUniqueEncodedField(vivifiedWrapper); vivifiedWrapper.addVirtualMethods( - synthesizeVirtualMethodsForVivifiedTypeWrapper( + synthesizeVirtualMethodsForWrapper( methods, vivifiedWrapperField, - m -> - synthesizeCfCodeForVivifiedTypeWrapper( - m, - vivifiedWrapperField.getReference(), + (method, field) -> + conversionCfProvider.generateVivifiedWrapperConversion( + method, + field, eventConsumer, - () -> processingContext.createUniqueContext(vivifiedWrapper)))); + () -> processingContext.createUniqueContext(wrapper)))); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java index 3d9d2b8..7f5e42e 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1833,6 +1833,9 @@ } public void shortenLiveRanges(IRCode code) { + if (options.testing.disableShortenLiveRanges) { + return; + } // Currently, we are only shortening the live range of ConstNumbers in the entry block // and ConstStrings with one user. // TODO(ager): Generalize this to shorten live ranges for more instructions? Currently @@ -3710,7 +3713,7 @@ // The javac fix for JDK-8272564 has to be rewritten back to invoke-virtual on Object if the // method with an Object signature is not defined on the interface. See // https://bugs.openjdk.java.net/browse/JDK-8272564 - public static void rewriteJdk8272564Fix(IRCode code, AppView<?> appView) { + public static void rewriteJdk8272564Fix(IRCode code, ProgramMethod context, AppView<?> appView) { DexItemFactory dexItemFactory = appView.dexItemFactory(); InstructionListIterator it = code.instructionListIterator(); while (it.hasNext()) { @@ -3718,10 +3721,15 @@ if (instruction.isInvokeInterface()) { InvokeInterface invoke = instruction.asInvokeInterface(); DexMethod method = invoke.getInvokedMethod(); - DexMethod objectMember = dexItemFactory.objectMembers.matchingPublicObjectMember(method); - if (objectMember != null && appView.definitionFor(method) == null) { - it.replaceCurrentInstruction( - new InvokeVirtual(objectMember, invoke.outValue(), invoke.arguments())); + DexClass clazz = appView.definitionFor(method.getHolderType(), context); + if (clazz == null || clazz.isInterface()) { + DexMethod objectMember = dexItemFactory.objectMembers.matchingPublicObjectMember(method); + // javac before JDK-8272564 would still use invoke interface if the method is defined + // directly on the interface reference, so mimic that by not rewriting. + if (objectMember != null && appView.definitionFor(method) == null) { + it.replaceCurrentInstruction( + new InvokeVirtual(objectMember, invoke.outValue(), invoke.arguments())); + } } } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java index 8839c13..bf34207 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -25,7 +25,6 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; -import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.logging.Log; @@ -190,7 +189,6 @@ // Double-check the entry block does not have catch handlers. // Otherwise, we need to split it before moving canonicalized const-string, which may throw. assert !code.entryBlock().hasCatchHandlers(); - final Position firstNonNonePosition = code.findFirstNonNonePosition(Position.syntheticNone()); FastSortedEntrySet<Instruction, List<Value>> entries = valuesDefinedByConstant.object2ObjectEntrySet(); // Sort the most frequently used constant first and exclude constant use only one time, such @@ -256,7 +254,6 @@ default: throw new Unreachable(); } - newConst.setPosition(firstNonNonePosition); insertCanonicalizedConstant(code, newConst); for (Value outValue : entry.getValue()) { outValue.replaceUsers(newConst.outValue()); @@ -280,7 +277,9 @@ // that can throw exceptions (since the value could be used on the exceptional edge). InstructionListIterator it = entryBlock.listIterator(code); while (it.hasNext()) { - if (!it.next().isArgument()) { + Instruction next = it.next(); + if (!next.isArgument()) { + canonicalizedConstant.setPosition(code.getEntryPosition()); it.previous(); break; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java index fa902a6..2cab3fb 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -1083,7 +1083,7 @@ if (instruction.isInvokeMethod()) { argumentTypes.add(argumentTypeFromValue(value, instruction.asInvokeMethod(), i)); } else { - argumentTypes.add(instruction.asBinop().getNumericType().dexTypeFor(dexItemFactory)); + argumentTypes.add(instruction.asBinop().getNumericType().toDexType(dexItemFactory)); } argumentsMap.add(argumentTypes.size() - 1); } @@ -1098,7 +1098,7 @@ } else { updateReturnValueState( instruction.outValue(), - instruction.asBinop().getNumericType().dexTypeFor(dexItemFactory)); + instruction.asBinop().getNumericType().toDexType(dexItemFactory)); } } } @@ -1738,7 +1738,7 @@ @Override public Position getCanonicalDebugPositionAtOffset(int offset) { - throw new Unreachable(); + return Position.syntheticNone(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java index dd73d7e..6e187ae 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -83,6 +83,7 @@ return new IRCode( appView.options(), checkNotZeroMethod, + code.getEntryPosition(), code.getBlocks(), code.valueNumberGenerator, code.basicBlockNumberGenerator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java index 7e0b884..3ad14fb 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -504,7 +504,7 @@ if (temp == null) { return null; } - DexType conversionType = conversion.to.dexTypeFor(factory); + DexType conversionType = conversion.to.toDexType(factory); if (conversionType == factory.booleanType) { return temp.intValue() != 0 ? 1 : 0; } else if (conversionType == factory.byteType) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java deleted file mode 100644 index afadf9f..0000000 --- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java +++ /dev/null
@@ -1,638 +0,0 @@ -// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -package com.android.tools.r8.ir.synthetic; - -import com.android.tools.r8.cf.code.CfArithmeticBinop; -import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode; -import com.android.tools.r8.cf.code.CfArrayLength; -import com.android.tools.r8.cf.code.CfArrayLoad; -import com.android.tools.r8.cf.code.CfArrayStore; -import com.android.tools.r8.cf.code.CfCheckCast; -import com.android.tools.r8.cf.code.CfConstNull; -import com.android.tools.r8.cf.code.CfConstNumber; -import com.android.tools.r8.cf.code.CfConstString; -import com.android.tools.r8.cf.code.CfFrame; -import com.android.tools.r8.cf.code.CfFrame.FrameType; -import com.android.tools.r8.cf.code.CfGoto; -import com.android.tools.r8.cf.code.CfIf; -import com.android.tools.r8.cf.code.CfIfCmp; -import com.android.tools.r8.cf.code.CfInstanceFieldRead; -import com.android.tools.r8.cf.code.CfInstanceFieldWrite; -import com.android.tools.r8.cf.code.CfInstanceOf; -import com.android.tools.r8.cf.code.CfInstruction; -import com.android.tools.r8.cf.code.CfInvoke; -import com.android.tools.r8.cf.code.CfLabel; -import com.android.tools.r8.cf.code.CfLoad; -import com.android.tools.r8.cf.code.CfNew; -import com.android.tools.r8.cf.code.CfNewArray; -import com.android.tools.r8.cf.code.CfReturn; -import com.android.tools.r8.cf.code.CfReturnVoid; -import com.android.tools.r8.cf.code.CfStackInstruction; -import com.android.tools.r8.cf.code.CfStaticFieldRead; -import com.android.tools.r8.cf.code.CfStore; -import com.android.tools.r8.cf.code.CfThrow; -import com.android.tools.r8.contexts.CompilationContext.UniqueContext; -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.CfCode; -import com.android.tools.r8.graph.DexEncodedField; -import com.android.tools.r8.graph.DexField; -import com.android.tools.r8.graph.DexItemFactory; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.graph.DexProto; -import com.android.tools.r8.graph.DexString; -import com.android.tools.r8.graph.DexType; -import com.android.tools.r8.ir.code.If; -import com.android.tools.r8.ir.code.MemberType; -import com.android.tools.r8.ir.code.NumericType; -import com.android.tools.r8.ir.code.ValueType; -import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter; -import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizer; -import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer; -import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer; -import com.android.tools.r8.utils.BooleanUtils; -import com.android.tools.r8.utils.collections.ImmutableDeque; -import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.function.Supplier; -import org.objectweb.asm.Opcodes; - -public abstract class DesugaredLibraryAPIConversionCfCodeProvider extends SyntheticCfCodeProvider { - - DesugaredLibraryAPIConversionCfCodeProvider(AppView<?> appView, DexType holder) { - super(appView, holder); - } - - DexType vivifiedTypeFor(DexType type) { - return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView); - } - - public static class APIConverterVivifiedWrapperCfCodeProvider - extends DesugaredLibraryAPIConversionCfCodeProvider { - - private final DexField wrapperField; - private final DexMethod forwardMethod; - private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizer; - private final boolean itfCall; - private final DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer; - private final Supplier<UniqueContext> contextSupplier; - - public APIConverterVivifiedWrapperCfCodeProvider( - AppView<?> appView, - DexMethod forwardMethod, - DexField wrapperField, - DesugaredLibraryWrapperSynthesizer wrapperSynthesizer, - boolean itfCall, - DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, - Supplier<UniqueContext> contextSupplier) { - super(appView, wrapperField.holder); - this.forwardMethod = forwardMethod; - this.wrapperField = wrapperField; - this.wrapperSynthesizer = wrapperSynthesizer; - this.itfCall = itfCall; - this.eventConsumer = eventConsumer; - this.contextSupplier = contextSupplier; - } - - @Override - public CfCode generateCfCode() { - DexItemFactory factory = appView.dexItemFactory(); - List<CfInstruction> instructions = new ArrayList<>(); - // Wrapped value is a vivified type. Method uses type as external. Forward method should - // use vivifiedTypes. - - instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0)); - instructions.add(new CfInstanceFieldRead(wrapperField)); - int index = 1; - int stackIndex = 1; - DexType[] newParameters = forwardMethod.proto.parameters.values.clone(); - for (DexType param : forwardMethod.proto.parameters.values) { - instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex)); - if (wrapperSynthesizer.shouldConvert(param, forwardMethod)) { - instructions.add( - new CfInvoke( - Opcodes.INVOKESTATIC, - conversionMethod(param, param, vivifiedTypeFor(param)), - false)); - newParameters[index - 1] = vivifiedTypeFor(param); - } - if (param == factory.longType || param == factory.doubleType) { - stackIndex++; - } - stackIndex++; - index++; - } - - DexType returnType = forwardMethod.proto.returnType; - DexType forwardMethodReturnType = - appView.typeRewriter.hasRewrittenType(returnType, appView) - ? vivifiedTypeFor(returnType) - : returnType; - - DexProto newProto = factory.createProto(forwardMethodReturnType, newParameters); - DexMethod newForwardMethod = - factory.createMethod(wrapperField.type, newProto, forwardMethod.name); - - if (itfCall) { - instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, newForwardMethod, true)); - } else { - instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newForwardMethod, false)); - } - - if (wrapperSynthesizer.shouldConvert(returnType, forwardMethod)) { - instructions.add( - new CfInvoke( - Opcodes.INVOKESTATIC, - conversionMethod(returnType, vivifiedTypeFor(returnType), returnType), - false)); - } - if (returnType == factory.voidType) { - instructions.add(new CfReturnVoid()); - } else { - instructions.add(new CfReturn(ValueType.fromDexType(returnType))); - } - return standardCfCodeFromInstructions(instructions); - } - - private DexMethod conversionMethod(DexType type, DexType srcType, DexType destType) { - return wrapperSynthesizer.getExistingProgramConversionMethod( - type, srcType, destType, eventConsumer, contextSupplier); - } - } - - public abstract static class AbstractAPIConverterWrapperCfCodeProvider - extends DesugaredLibraryAPIConversionCfCodeProvider { - - DexMethod forwardMethod; - DesugaredLibraryWrapperSynthesizer wrapperSynthesizor; - boolean itfCall; - - public AbstractAPIConverterWrapperCfCodeProvider( - AppView<?> appView, - DexType holder, - DexMethod forwardMethod, - DesugaredLibraryWrapperSynthesizer wrapperSynthesizor, - boolean itfCall) { - super(appView, holder); - this.forwardMethod = forwardMethod; - this.wrapperSynthesizor = wrapperSynthesizor; - this.itfCall = itfCall; - } - - abstract void generatePushReceiver(List<CfInstruction> instructions); - - abstract DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType); - - @Override - public CfCode generateCfCode() { - DexItemFactory factory = appView.dexItemFactory(); - List<CfInstruction> instructions = new ArrayList<>(); - // Wrapped value is a type. Method uses vivifiedTypes as external. Forward method should - // use types. - - generatePushReceiver(instructions); - int stackIndex = 1; - for (DexType param : forwardMethod.proto.parameters.values) { - instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex)); - if (wrapperSynthesizor.shouldConvert(param, forwardMethod)) { - instructions.add( - new CfInvoke( - Opcodes.INVOKESTATIC, - ensureConversionMethod(param, vivifiedTypeFor(param), param), - false)); - } - if (param == factory.longType || param == factory.doubleType) { - stackIndex++; - } - stackIndex++; - } - - if (itfCall) { - instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, forwardMethod, true)); - } else { - instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, forwardMethod, false)); - } - - DexType returnType = forwardMethod.proto.returnType; - if (wrapperSynthesizor.shouldConvert(returnType, forwardMethod)) { - instructions.add( - new CfInvoke( - Opcodes.INVOKESTATIC, - ensureConversionMethod(returnType, returnType, vivifiedTypeFor(returnType)), - false)); - returnType = vivifiedTypeFor(returnType); - } - if (returnType == factory.voidType) { - instructions.add(new CfReturnVoid()); - } else { - instructions.add(new CfReturn(ValueType.fromDexType(returnType))); - } - return standardCfCodeFromInstructions(instructions); - } - } - - public static class APICallbackWrapperCfCodeProvider - extends AbstractAPIConverterWrapperCfCodeProvider { - - private final DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer; - private final Supplier<UniqueContext> contextSupplier; - - public APICallbackWrapperCfCodeProvider( - AppView<?> appView, - DexMethod forwardMethod, - DesugaredLibraryWrapperSynthesizer wrapperSynthesizor, - boolean itfCall, - DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer, - Supplier<UniqueContext> contextSupplier) { - super(appView, forwardMethod.holder, forwardMethod, wrapperSynthesizor, itfCall); - this.eventConsumer = eventConsumer; - this.contextSupplier = contextSupplier; - } - - @Override - void generatePushReceiver(List<CfInstruction> instructions) { - instructions.add(new CfLoad(ValueType.fromDexType(forwardMethod.holder), 0)); - } - - @Override - DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) { - return wrapperSynthesizor.ensureConversionMethod( - type, srcType, destType, eventConsumer, contextSupplier); - } - } - - public static class APIConverterWrapperCfCodeProvider - extends AbstractAPIConverterWrapperCfCodeProvider { - - private final DexField wrapperField; - private final DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer; - private final Supplier<UniqueContext> contextSupplier; - - public APIConverterWrapperCfCodeProvider( - AppView<?> appView, - DexMethod forwardMethod, - DexField wrapperField, - DesugaredLibraryWrapperSynthesizer wrapperSynthesizor, - boolean itfCall, - DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer, - Supplier<UniqueContext> contextSupplier) { - super(appView, wrapperField.holder, forwardMethod, wrapperSynthesizor, itfCall); - this.wrapperField = wrapperField; - this.eventConsumer = eventConsumer; - this.contextSupplier = contextSupplier; - } - - @Override - void generatePushReceiver(List<CfInstruction> instructions) { - instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0)); - instructions.add(new CfInstanceFieldRead(wrapperField)); - } - - @Override - DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) { - return wrapperSynthesizor.getExistingProgramConversionMethod( - type, srcType, destType, eventConsumer, contextSupplier); - } - } - - public static class APIConverterWrapperConversionCfCodeProvider extends SyntheticCfCodeProvider { - - DexField reverseWrapperField; - DexField wrapperField; - - public APIConverterWrapperConversionCfCodeProvider( - AppView<?> appView, DexField reverseWrapperField, DexField wrapperField) { - super(appView, wrapperField.holder); - this.reverseWrapperField = reverseWrapperField; - this.wrapperField = wrapperField; - } - - @Override - public CfCode generateCfCode() { - DexItemFactory factory = appView.dexItemFactory(); - List<CfInstruction> instructions = new ArrayList<>(); - - DexType argType = wrapperField.type; - ImmutableInt2ReferenceSortedMap<FrameType> locals = - ImmutableInt2ReferenceSortedMap.<FrameType>builder() - .put(0, FrameType.initialized(argType)) - .build(); - - // if (arg == null) { return null }; - CfLabel nullDest = new CfLabel(); - instructions.add(new CfLoad(ValueType.fromDexType(argType), 0)); - instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest)); - instructions.add(new CfConstNull()); - instructions.add(new CfReturn(ValueType.OBJECT)); - instructions.add(nullDest); - instructions.add(new CfFrame(locals, ImmutableDeque.of())); - - // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField}; - assert reverseWrapperField != null; - CfLabel unwrapDest = new CfLabel(); - instructions.add(new CfLoad(ValueType.fromDexType(argType), 0)); - instructions.add(new CfInstanceOf(reverseWrapperField.holder)); - instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest)); - instructions.add(new CfLoad(ValueType.fromDexType(argType), 0)); - instructions.add(new CfCheckCast(reverseWrapperField.holder)); - instructions.add(new CfInstanceFieldRead(reverseWrapperField)); - instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type))); - instructions.add(unwrapDest); - instructions.add(new CfFrame(locals, ImmutableDeque.of())); - - // return new Wrapper(wrappedValue); - instructions.add(new CfNew(wrapperField.holder)); - instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP)); - instructions.add(new CfLoad(ValueType.fromDexType(argType), 0)); - instructions.add( - new CfInvoke( - Opcodes.INVOKESPECIAL, - factory.createMethod( - wrapperField.holder, - factory.createProto(factory.voidType, argType), - factory.constructorMethodName), - false)); - instructions.add(new CfReturn(ValueType.fromDexType(wrapperField.holder))); - return standardCfCodeFromInstructions(instructions); - } - } - - public static class APIConversionCfCodeProvider extends SyntheticCfCodeProvider { - - private final CfInvoke initialInvoke; - private final DexMethod returnConversion; - private final DexMethod[] parameterConversions; - - public APIConversionCfCodeProvider( - AppView<?> appView, - DexType holder, - CfInvoke initialInvoke, - DexMethod returnConversion, - DexMethod[] parameterConversions) { - super(appView, holder); - this.initialInvoke = initialInvoke; - this.returnConversion = returnConversion; - this.parameterConversions = parameterConversions; - } - - @Override - public CfCode generateCfCode() { - DexMethod invokedMethod = initialInvoke.getMethod(); - DexMethod convertedMethod = - DesugaredLibraryAPIConverter.getConvertedAPI( - invokedMethod, returnConversion, parameterConversions, appView); - - List<CfInstruction> instructions = new ArrayList<>(); - - boolean isStatic = initialInvoke.getOpcode() == Opcodes.INVOKESTATIC; - if (!isStatic) { - instructions.add(new CfLoad(ValueType.fromDexType(invokedMethod.holder), 0)); - } - int receiverShift = BooleanUtils.intValue(!isStatic); - int stackIndex = 0; - for (int i = 0; i < invokedMethod.getArity(); i++) { - DexType param = invokedMethod.getParameter(i); - instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex + receiverShift)); - if (parameterConversions[i] != null) { - instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false)); - } - if (param == appView.dexItemFactory().longType - || param == appView.dexItemFactory().doubleType) { - stackIndex++; - } - stackIndex++; - } - - // Actual call to converted value. - instructions.add( - new CfInvoke(initialInvoke.getOpcode(), convertedMethod, initialInvoke.isInterface())); - - // Return conversion. - if (returnConversion != null) { - instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false)); - } - - if (invokedMethod.getReturnType().isVoidType()) { - instructions.add(new CfReturnVoid()); - } else { - instructions.add(new CfReturn(ValueType.fromDexType(invokedMethod.getReturnType()))); - } - return standardCfCodeFromInstructions(instructions); - } - } - - public static class ArrayConversionCfCodeProvider extends SyntheticCfCodeProvider { - - private final DexType typeArray; - private final DexType convertedTypeArray; - private final DexMethod conversion; - - public ArrayConversionCfCodeProvider( - AppView<?> appView, - DexType holder, - DexType typeArray, - DexType convertedTypeArray, - DexMethod conversion) { - super(appView, holder); - this.typeArray = typeArray; - this.convertedTypeArray = convertedTypeArray; - this.conversion = conversion; - } - - @Override - public CfCode generateCfCode() { - DexItemFactory factory = appView.dexItemFactory(); - List<CfInstruction> instructions = new ArrayList<>(); - - // if (arg == null) { return null; } - instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0)); - instructions.add(new CfConstNull()); - CfLabel nonNull = new CfLabel(); - instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, nonNull)); - instructions.add(new CfConstNull()); - instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray))); - instructions.add(nonNull); - instructions.add( - new CfFrame( - ImmutableInt2ReferenceSortedMap.<FrameType>builder() - .put(0, FrameType.initialized(typeArray)) - .build(), - ImmutableDeque.of())); - - ImmutableInt2ReferenceSortedMap<FrameType> locals = - ImmutableInt2ReferenceSortedMap.<FrameType>builder() - .put(0, FrameType.initialized(typeArray)) - .put(1, FrameType.initialized(factory.intType)) - .put(2, FrameType.initialized(convertedTypeArray)) - .put(3, FrameType.initialized(factory.intType)) - .build(); - - // int t1 = arg.length; - instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0)); - instructions.add(new CfArrayLength()); - instructions.add(new CfStore(ValueType.INT, 1)); - // ConvertedType[] t2 = new ConvertedType[t1]; - instructions.add(new CfLoad(ValueType.INT, 1)); - instructions.add(new CfNewArray(convertedTypeArray)); - instructions.add(new CfStore(ValueType.fromDexType(convertedTypeArray), 2)); - // int t3 = 0; - instructions.add(new CfConstNumber(0, ValueType.INT)); - instructions.add(new CfStore(ValueType.INT, 3)); - // while (t3 < t1) { - CfLabel returnLabel = new CfLabel(); - CfLabel loopLabel = new CfLabel(); - instructions.add(loopLabel); - instructions.add(new CfFrame(locals, ImmutableDeque.of())); - instructions.add(new CfLoad(ValueType.INT, 3)); - instructions.add(new CfLoad(ValueType.INT, 1)); - instructions.add(new CfIfCmp(If.Type.GE, ValueType.INT, returnLabel)); - // t2[t3] = convert(arg[t3]); - instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2)); - instructions.add(new CfLoad(ValueType.INT, 3)); - instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0)); - instructions.add(new CfLoad(ValueType.INT, 3)); - instructions.add(new CfArrayLoad(MemberType.OBJECT)); - instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, conversion, false)); - instructions.add(new CfArrayStore(MemberType.OBJECT)); - // t3 = t3 + 1; } - instructions.add(new CfLoad(ValueType.INT, 3)); - instructions.add(new CfConstNumber(1, ValueType.INT)); - instructions.add(new CfArithmeticBinop(Opcode.Add, NumericType.INT)); - instructions.add(new CfStore(ValueType.INT, 3)); - instructions.add(new CfGoto(loopLabel)); - // return t2; - instructions.add(returnLabel); - instructions.add(new CfFrame(locals, ImmutableDeque.of())); - instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2)); - instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray))); - return standardCfCodeFromInstructions(instructions); - } - } - - public static class EnumConversionCfCodeProvider extends SyntheticCfCodeProvider { - - private final Iterable<DexEncodedField> enumFields; - private final DexType enumType; - private final DexType convertedType; - - public EnumConversionCfCodeProvider( - AppView<?> appView, - DexType holder, - Iterable<DexEncodedField> enumFields, - DexType enumType, - DexType convertedType) { - super(appView, holder); - this.enumFields = enumFields; - this.enumType = enumType; - this.convertedType = convertedType; - } - - @Override - public CfCode generateCfCode() { - DexItemFactory factory = appView.dexItemFactory(); - List<CfInstruction> instructions = new ArrayList<>(); - - ImmutableInt2ReferenceSortedMap<FrameType> locals = - ImmutableInt2ReferenceSortedMap.<FrameType>builder() - .put(0, FrameType.initialized(enumType)) - .build(); - - // if (arg == null) { return null; } - instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0)); - instructions.add(new CfConstNull()); - CfLabel nonNull = new CfLabel(); - instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, nonNull)); - instructions.add(new CfConstNull()); - instructions.add(new CfReturn(ValueType.fromDexType(convertedType))); - instructions.add(nonNull); - instructions.add(new CfFrame(locals, ImmutableDeque.of())); - - // if (arg == enumType.enumField1) { return convertedType.enumField1; } - Iterator<DexEncodedField> iterator = enumFields.iterator(); - while (iterator.hasNext()) { - DexEncodedField enumField = iterator.next(); - CfLabel notEqual = new CfLabel(); - if (iterator.hasNext()) { - instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0)); - instructions.add( - new CfStaticFieldRead(factory.createField(enumType, enumType, enumField.getName()))); - instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, notEqual)); - } - instructions.add( - new CfStaticFieldRead( - factory.createField(convertedType, convertedType, enumField.getName()))); - instructions.add(new CfReturn(ValueType.fromDexType(convertedType))); - if (iterator.hasNext()) { - instructions.add(notEqual); - instructions.add(new CfFrame(locals, ImmutableDeque.of())); - } - } - return standardCfCodeFromInstructions(instructions); - } - } - - public static class APIConverterConstructorCfCodeProvider extends SyntheticCfCodeProvider { - - private final DexField wrapperField; - private final DexType superType; - - public APIConverterConstructorCfCodeProvider( - AppView<?> appView, DexField wrapperField, DexType superType) { - super(appView, wrapperField.holder); - this.wrapperField = wrapperField; - this.superType = superType; - } - - @Override - public CfCode generateCfCode() { - DexItemFactory factory = appView.dexItemFactory(); - List<CfInstruction> instructions = new ArrayList<>(); - instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0)); - instructions.add( - new CfInvoke( - Opcodes.INVOKESPECIAL, - factory.createMethod( - superType, factory.createProto(factory.voidType), factory.constructorMethodName), - false)); - instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0)); - instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.type), 1)); - instructions.add(new CfInstanceFieldWrite(wrapperField)); - instructions.add(new CfReturnVoid()); - return standardCfCodeFromInstructions(instructions); - } - } - - public static class APIConverterThrowRuntimeExceptionCfCodeProvider - extends SyntheticCfCodeProvider { - - DexString message; - - public APIConverterThrowRuntimeExceptionCfCodeProvider( - AppView<?> appView, DexString message, DexType holder) { - super(appView, holder); - this.message = message; - } - - @Override - public CfCode generateCfCode() { - DexItemFactory factory = appView.dexItemFactory(); - List<CfInstruction> instructions = new ArrayList<>(); - instructions.add(new CfNew(factory.runtimeExceptionType)); - instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP)); - instructions.add(new CfConstString(message)); - instructions.add( - new CfInvoke( - Opcodes.INVOKESPECIAL, - factory.createMethod( - factory.runtimeExceptionType, - factory.createProto(factory.voidType, factory.stringType), - factory.constructorMethodName), - false)); - instructions.add(new CfThrow()); - return standardCfCodeFromInstructions(instructions); - } - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java index c81383c..8e0d7d4 100644 --- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -207,7 +207,7 @@ @Override public Position getCanonicalDebugPositionAtOffset(int offset) { - throw new Unreachable(); + return Position.syntheticNone(); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/APIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/APIConversionCfCodeProvider.java new file mode 100644 index 0000000..5932c0b --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/APIConversionCfCodeProvider.java
@@ -0,0 +1,151 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.synthetic.apiconverter; + +import com.android.tools.r8.cf.code.CfInstanceFieldRead; +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfInvoke; +import com.android.tools.r8.cf.code.CfLoad; +import com.android.tools.r8.cf.code.CfReturn; +import com.android.tools.r8.cf.code.CfReturnVoid; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.code.ValueType; +import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider; +import com.android.tools.r8.utils.BooleanUtils; +import java.util.ArrayList; +import java.util.List; +import org.objectweb.asm.Opcodes; + +public class APIConversionCfCodeProvider extends SyntheticCfCodeProvider { + + private final DexMethod forwardMethod; + private final boolean itfCall; + private final DexMethod returnConversion; + private final DexMethod[] parameterConversions; + // By default, the method is forwarded to the receiver (unless static), if this is set, the + // method is forwarded on this field on the receiver. + private final int forwardCallOpcode; + private final DexField forwardFieldOrNull; + + public APIConversionCfCodeProvider( + AppView<?> appView, + DexType holder, + DexMethod forwardMethod, + boolean itfCall, + DexMethod returnConversion, + DexMethod[] parameterConversions) { + super(appView, holder); + this.forwardMethod = forwardMethod; + this.itfCall = itfCall; + this.returnConversion = returnConversion; + this.parameterConversions = parameterConversions; + this.forwardCallOpcode = defaultForwardCallOpcode(itfCall); + this.forwardFieldOrNull = null; + } + + public APIConversionCfCodeProvider( + AppView<?> appView, + DexType holder, + DexMethod forwardMethod, + boolean itfCall, + DexMethod returnConversion, + DexMethod[] parameterConversions, + int forwardCallOpcode) { + super(appView, holder); + this.forwardMethod = forwardMethod; + this.itfCall = itfCall; + this.returnConversion = returnConversion; + this.parameterConversions = parameterConversions; + this.forwardCallOpcode = forwardCallOpcode; + this.forwardFieldOrNull = null; + } + + public APIConversionCfCodeProvider( + AppView<?> appView, + DexType holder, + DexMethod forwardMethod, + boolean itfCall, + DexMethod returnConversion, + DexMethod[] parameterConversions, + DexField forwardFieldOrNull) { + super(appView, holder); + this.forwardMethod = forwardMethod; + this.itfCall = itfCall; + this.returnConversion = returnConversion; + this.parameterConversions = parameterConversions; + this.forwardCallOpcode = defaultForwardCallOpcode(itfCall); + this.forwardFieldOrNull = forwardFieldOrNull; + } + + private int defaultForwardCallOpcode(boolean itfCall) { + return itfCall ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL; + } + + @Override + public CfCode generateCfCode() { + List<CfInstruction> instructions = new ArrayList<>(); + boolean isStatic = forwardCallOpcode == Opcodes.INVOKESTATIC; + generatePushReceiver(instructions, isStatic); + generateParameterConvertAndLoads(instructions, isStatic); + generateForwardingCall(instructions); + generateReturnConversion(instructions); + generateReturn(instructions); + return standardCfCodeFromInstructions(instructions); + } + + private void generateReturn(List<CfInstruction> instructions) { + if (forwardMethod.getReturnType().isVoidType()) { + instructions.add(new CfReturnVoid()); + } else { + ValueType valueType = valueTypeFromForwardMethod(forwardMethod.getReturnType()); + instructions.add(new CfReturn(valueType)); + } + } + + private void generateReturnConversion(List<CfInstruction> instructions) { + if (returnConversion != null) { + instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false)); + } + } + + private void generateForwardingCall(List<CfInstruction> instructions) { + instructions.add(new CfInvoke(forwardCallOpcode, forwardMethod, itfCall)); + } + + private void generateParameterConvertAndLoads( + List<CfInstruction> instructions, boolean isStatic) { + int stackIndex = BooleanUtils.intValue(!isStatic); + for (int i = 0; i < forwardMethod.getArity(); i++) { + ValueType valueType = valueTypeFromForwardMethod(forwardMethod.getParameter(i)); + instructions.add(new CfLoad(valueType, stackIndex)); + if (parameterConversions[i] != null) { + instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false)); + } + if (valueType.isWide()) { + stackIndex++; + } + stackIndex++; + } + } + + private void generatePushReceiver(List<CfInstruction> instructions, boolean isStatic) { + if (!isStatic) { + instructions.add(new CfLoad(ValueType.OBJECT, 0)); + if (forwardFieldOrNull != null) { + instructions.add(new CfInstanceFieldRead(forwardFieldOrNull)); + } + } + } + + private ValueType valueTypeFromForwardMethod(DexType type) { + // We note that the type may not be exact, we can have a vivified type instead of the expected + // type, however, the valueType is correct (primitive types are never converted). + return ValueType.fromDexType(type); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java new file mode 100644 index 0000000..98ec596 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
@@ -0,0 +1,260 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.synthetic.apiconverter; + +import com.android.tools.r8.cf.code.CfArithmeticBinop; +import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode; +import com.android.tools.r8.cf.code.CfArrayLength; +import com.android.tools.r8.cf.code.CfArrayLoad; +import com.android.tools.r8.cf.code.CfArrayStore; +import com.android.tools.r8.cf.code.CfCheckCast; +import com.android.tools.r8.cf.code.CfConstNull; +import com.android.tools.r8.cf.code.CfConstNumber; +import com.android.tools.r8.cf.code.CfFrame; +import com.android.tools.r8.cf.code.CfFrame.FrameType; +import com.android.tools.r8.cf.code.CfGoto; +import com.android.tools.r8.cf.code.CfIf; +import com.android.tools.r8.cf.code.CfIfCmp; +import com.android.tools.r8.cf.code.CfInstanceFieldRead; +import com.android.tools.r8.cf.code.CfInstanceOf; +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfInvoke; +import com.android.tools.r8.cf.code.CfLabel; +import com.android.tools.r8.cf.code.CfLoad; +import com.android.tools.r8.cf.code.CfNew; +import com.android.tools.r8.cf.code.CfNewArray; +import com.android.tools.r8.cf.code.CfReturn; +import com.android.tools.r8.cf.code.CfStackInstruction; +import com.android.tools.r8.cf.code.CfStaticFieldRead; +import com.android.tools.r8.cf.code.CfStore; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.code.If; +import com.android.tools.r8.ir.code.MemberType; +import com.android.tools.r8.ir.code.NumericType; +import com.android.tools.r8.ir.code.ValueType; +import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider; +import com.android.tools.r8.utils.collections.ImmutableDeque; +import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.objectweb.asm.Opcodes; + +public abstract class NullableConversionCfCodeProvider extends SyntheticCfCodeProvider { + + protected NullableConversionCfCodeProvider(AppView<?> appView, DexType holder) { + super(appView, holder); + } + + void generateNullCheck(List<CfInstruction> instructions) { + CfLabel nullDest = new CfLabel(); + instructions.add(new CfLoad(ValueType.OBJECT, 0)); + instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest)); + instructions.add(new CfConstNull()); + instructions.add(new CfReturn(ValueType.OBJECT)); + instructions.add(nullDest); + } + + public static class ArrayConversionCfCodeProvider extends NullableConversionCfCodeProvider { + + private final DexType typeArray; + private final DexType convertedTypeArray; + private final DexMethod conversion; + + public ArrayConversionCfCodeProvider( + AppView<?> appView, + DexType holder, + DexType typeArray, + DexType convertedTypeArray, + DexMethod conversion) { + super(appView, holder); + this.typeArray = typeArray; + this.convertedTypeArray = convertedTypeArray; + this.conversion = conversion; + } + + @Override + public CfCode generateCfCode() { + DexItemFactory factory = appView.dexItemFactory(); + List<CfInstruction> instructions = new ArrayList<>(); + + // if (arg == null) { return null; } + generateNullCheck(instructions); + instructions.add( + new CfFrame( + ImmutableInt2ReferenceSortedMap.<FrameType>builder() + .put(0, FrameType.initialized(typeArray)) + .build(), + ImmutableDeque.of())); + + ImmutableInt2ReferenceSortedMap<FrameType> locals = + ImmutableInt2ReferenceSortedMap.<FrameType>builder() + .put(0, FrameType.initialized(typeArray)) + .put(1, FrameType.initialized(factory.intType)) + .put(2, FrameType.initialized(convertedTypeArray)) + .put(3, FrameType.initialized(factory.intType)) + .build(); + + // int t1 = arg.length; + instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0)); + instructions.add(new CfArrayLength()); + instructions.add(new CfStore(ValueType.INT, 1)); + // ConvertedType[] t2 = new ConvertedType[t1]; + instructions.add(new CfLoad(ValueType.INT, 1)); + instructions.add(new CfNewArray(convertedTypeArray)); + instructions.add(new CfStore(ValueType.fromDexType(convertedTypeArray), 2)); + // int t3 = 0; + instructions.add(new CfConstNumber(0, ValueType.INT)); + instructions.add(new CfStore(ValueType.INT, 3)); + // while (t3 < t1) { + CfLabel returnLabel = new CfLabel(); + CfLabel loopLabel = new CfLabel(); + instructions.add(loopLabel); + instructions.add(new CfFrame(locals, ImmutableDeque.of())); + instructions.add(new CfLoad(ValueType.INT, 3)); + instructions.add(new CfLoad(ValueType.INT, 1)); + instructions.add(new CfIfCmp(If.Type.GE, ValueType.INT, returnLabel)); + // t2[t3] = convert(arg[t3]); + instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2)); + instructions.add(new CfLoad(ValueType.INT, 3)); + instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0)); + instructions.add(new CfLoad(ValueType.INT, 3)); + instructions.add(new CfArrayLoad(MemberType.OBJECT)); + instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, conversion, false)); + instructions.add(new CfArrayStore(MemberType.OBJECT)); + // t3 = t3 + 1; } + instructions.add(new CfLoad(ValueType.INT, 3)); + instructions.add(new CfConstNumber(1, ValueType.INT)); + instructions.add(new CfArithmeticBinop(Opcode.Add, NumericType.INT)); + instructions.add(new CfStore(ValueType.INT, 3)); + instructions.add(new CfGoto(loopLabel)); + // return t2; + instructions.add(returnLabel); + instructions.add(new CfFrame(locals, ImmutableDeque.of())); + instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2)); + instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray))); + return standardCfCodeFromInstructions(instructions); + } + } + + public static class EnumConversionCfCodeProvider extends NullableConversionCfCodeProvider { + + private final Iterable<DexEncodedField> enumFields; + private final DexType enumType; + private final DexType convertedType; + + public EnumConversionCfCodeProvider( + AppView<?> appView, + DexType holder, + Iterable<DexEncodedField> enumFields, + DexType enumType, + DexType convertedType) { + super(appView, holder); + this.enumFields = enumFields; + this.enumType = enumType; + this.convertedType = convertedType; + } + + @Override + public CfCode generateCfCode() { + DexItemFactory factory = appView.dexItemFactory(); + List<CfInstruction> instructions = new ArrayList<>(); + + ImmutableInt2ReferenceSortedMap<FrameType> locals = + ImmutableInt2ReferenceSortedMap.<FrameType>builder() + .put(0, FrameType.initialized(enumType)) + .build(); + + // if (arg == null) { return null; } + generateNullCheck(instructions); + instructions.add(new CfFrame(locals, ImmutableDeque.of())); + + // if (arg == enumType.enumField1) { return convertedType.enumField1; } + Iterator<DexEncodedField> iterator = enumFields.iterator(); + while (iterator.hasNext()) { + DexEncodedField enumField = iterator.next(); + CfLabel notEqual = new CfLabel(); + if (iterator.hasNext()) { + instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0)); + instructions.add( + new CfStaticFieldRead(factory.createField(enumType, enumType, enumField.getName()))); + instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, notEqual)); + } + instructions.add( + new CfStaticFieldRead( + factory.createField(convertedType, convertedType, enumField.getName()))); + instructions.add(new CfReturn(ValueType.fromDexType(convertedType))); + if (iterator.hasNext()) { + instructions.add(notEqual); + instructions.add(new CfFrame(locals, ImmutableDeque.of())); + } + } + return standardCfCodeFromInstructions(instructions); + } + } + + public static class WrapperConversionCfCodeProvider extends NullableConversionCfCodeProvider { + + DexField reverseWrapperField; + DexField wrapperField; + + public WrapperConversionCfCodeProvider( + AppView<?> appView, DexField reverseWrapperField, DexField wrapperField) { + super(appView, wrapperField.holder); + this.reverseWrapperField = reverseWrapperField; + this.wrapperField = wrapperField; + } + + @Override + public CfCode generateCfCode() { + DexItemFactory factory = appView.dexItemFactory(); + List<CfInstruction> instructions = new ArrayList<>(); + + DexType argType = wrapperField.type; + ImmutableInt2ReferenceSortedMap<FrameType> locals = + ImmutableInt2ReferenceSortedMap.<FrameType>builder() + .put(0, FrameType.initialized(argType)) + .build(); + + // if (arg == null) { return null }; + generateNullCheck(instructions); + instructions.add(new CfFrame(locals, ImmutableDeque.of())); + + // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField}; + assert reverseWrapperField != null; + CfLabel unwrapDest = new CfLabel(); + instructions.add(new CfLoad(ValueType.fromDexType(argType), 0)); + instructions.add(new CfInstanceOf(reverseWrapperField.holder)); + instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest)); + instructions.add(new CfLoad(ValueType.fromDexType(argType), 0)); + instructions.add(new CfCheckCast(reverseWrapperField.holder)); + instructions.add(new CfInstanceFieldRead(reverseWrapperField)); + instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type))); + instructions.add(unwrapDest); + instructions.add(new CfFrame(locals, ImmutableDeque.of())); + + // return new Wrapper(wrappedValue); + instructions.add(new CfNew(wrapperField.holder)); + instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP)); + instructions.add(new CfLoad(ValueType.fromDexType(argType), 0)); + instructions.add( + new CfInvoke( + Opcodes.INVOKESPECIAL, + factory.createMethod( + wrapperField.holder, + factory.createProto(factory.voidType, argType), + factory.constructorMethodName), + false)); + instructions.add(new CfReturn(ValueType.fromDexType(wrapperField.holder))); + return standardCfCodeFromInstructions(instructions); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/WrapperConstructorCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/WrapperConstructorCfCodeProvider.java new file mode 100644 index 0000000..94ba378 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/WrapperConstructorCfCodeProvider.java
@@ -0,0 +1,52 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.ir.synthetic.apiconverter; + +import com.android.tools.r8.cf.code.CfInstanceFieldWrite; +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfInvoke; +import com.android.tools.r8.cf.code.CfLoad; +import com.android.tools.r8.cf.code.CfReturnVoid; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.code.ValueType; +import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider; +import java.util.ArrayList; +import java.util.List; +import org.objectweb.asm.Opcodes; + +public class WrapperConstructorCfCodeProvider extends SyntheticCfCodeProvider { + + private final DexField wrapperField; + private final DexType superType; + + public WrapperConstructorCfCodeProvider( + AppView<?> appView, DexField wrapperField, DexType superType) { + super(appView, wrapperField.holder); + this.wrapperField = wrapperField; + this.superType = superType; + } + + @Override + public CfCode generateCfCode() { + DexItemFactory factory = appView.dexItemFactory(); + List<CfInstruction> instructions = new ArrayList<>(); + instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0)); + instructions.add( + new CfInvoke( + Opcodes.INVOKESPECIAL, + factory.createMethod( + superType, factory.createProto(factory.voidType), factory.constructorMethodName), + false)); + instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0)); + instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.type), 1)); + instructions.add(new CfInstanceFieldWrite(wrapperField)); + instructions.add(new CfReturnVoid()); + return standardCfCodeFromInstructions(instructions); + } +}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java index 7572341..dad22fa 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; +import java.util.function.BiFunction; import java.util.function.Function; public class BottomCfFrameState extends CfFrameState { @@ -43,7 +44,9 @@ @Override public CfFrameState pop( - AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn) { + AppView<?> appView, + FrameType expectedType, + BiFunction<CfFrameState, FrameType, CfFrameState> fn) { return error(); }
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java index ec899a2..01bda85 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
@@ -11,6 +11,10 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState; +import com.android.tools.r8.ir.code.MemberType; +import com.android.tools.r8.ir.code.NumericType; +import com.android.tools.r8.ir.code.ValueType; +import java.util.function.BiFunction; import java.util.function.Function; public abstract class CfFrameState extends AbstractState<CfFrameState> { @@ -38,7 +42,9 @@ public abstract CfFrameState pop(AppView<?> appView, FrameType expectedType); public abstract CfFrameState pop( - AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn); + AppView<?> appView, + FrameType expectedType, + BiFunction<CfFrameState, FrameType, CfFrameState> fn); public abstract CfFrameState pop(AppView<?> appView, FrameType... expectedTypes); @@ -49,10 +55,34 @@ public abstract CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes); + public final CfFrameState popInitialized(AppView<?> appView, MemberType memberType) { + return pop(appView, FrameType.fromMemberType(memberType, appView.dexItemFactory())); + } + + public final CfFrameState popInitialized(AppView<?> appView, NumericType expectedType) { + return popInitialized(appView, expectedType.toDexType(appView.dexItemFactory())); + } + + // TODO(b/214496607): Pushing a value should return an error if the stack grows larger than the + // max stack height. public abstract CfFrameState push(DexType type); + // TODO(b/214496607): Pushing a value should return an error if the stack grows larger than the + // max stack height. public abstract CfFrameState push(FrameType frameType); + public final CfFrameState push(AppView<?> appView, MemberType memberType) { + return push(FrameType.fromMemberType(memberType, appView.dexItemFactory())); + } + + public final CfFrameState push(AppView<?> appView, NumericType numericType) { + return push(numericType.toDexType(appView.dexItemFactory())); + } + + public final CfFrameState push(AppView<?> appView, ValueType valueType) { + return push(valueType.toDexType(appView.dexItemFactory())); + } + @Override public final CfFrameState join(CfFrameState state) { // TODO(b/214496607): Implement join.
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java index 63654b3..4abdc0f 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
@@ -18,6 +18,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.Function; public class ConcreteCfFrameState extends CfFrameState { @@ -76,16 +77,18 @@ @Override public CfFrameState pop(AppView<?> appView, FrameType expectedType) { - return pop(appView, expectedType, ignore -> this); + return pop(appView, expectedType, (newFrame, ignore) -> newFrame); } @Override public CfFrameState pop( - AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn) { + AppView<?> appView, + FrameType expectedType, + BiFunction<CfFrameState, FrameType, CfFrameState> fn) { return pop( frameType -> CfAssignability.isAssignable(frameType, expectedType, appView) - ? fn.apply(frameType) + ? fn.apply(this, frameType) : error()); }
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java index 7e71f93..cdbeba2 100644 --- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java +++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.graph.ProgramMethod; +import java.util.function.BiFunction; import java.util.function.Function; /** An analysis state representing that the code does not type check. */ @@ -44,7 +45,9 @@ @Override public CfFrameState pop( - AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn) { + AppView<?> appView, + FrameType expectedType, + BiFunction<CfFrameState, FrameType, CfFrameState> fn) { return this; }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java index 4b9385d..f6959c6 100644 --- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java +++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -260,9 +260,17 @@ private DexAnnotationElement rewriteAnnotationElement( DexType annotationType, DexAnnotationElement original) { - DexClass definition = appView.definitionFor(annotationType); + // The dalvik.annotation.AnnotationDefault is typically not on bootclasspath. However, if it + // is present, the definition does not define the 'value' getter but that is the spec: + // https://source.android.com/devices/tech/dalvik/dex-format#dalvik-annotation-default + // If the annotation matches the structural requirement keep it. + if (appView.dexItemFactory().annotationDefault.equals(annotationType) + && appView.dexItemFactory().valueString.equals(original.name)) { + return original; + } // We cannot strip annotations where we cannot look up the definition, because this will break // apps that rely on the annotation to exist. See b/134766810 for more information. + DexClass definition = appView.definitionFor(annotationType); if (definition == null) { return original; }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index 47baa0b..a2c0d26 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -4669,6 +4669,8 @@ defaultInitializer.getReference(), defaultInitializer, graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer)); + applyMinimumKeepInfoWhenLiveOrTargeted( + defaultInitializer, KeepMethodInfo.newEmptyJoiner().disallowOptimization()); } } }
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java new file mode 100644 index 0000000..00412d9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -0,0 +1,313 @@ +// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils; + +import com.android.tools.r8.ArchiveClassFileProvider; +import com.android.tools.r8.DiagnosticsHandler; +import com.android.tools.r8.Keep; +import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar; +import com.android.tools.r8.origin.PathOrigin; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Provides a mappings of classes to modules. The structure of the input file is as follows: + * packageOrClass:module + * + * <p>Lines with a # prefix are ignored. + * + * <p>We will do most specific matching, i.e., + * <pre> + * com.google.foobar.*:feature2 + * com.google.*:base + * </pre> + * will put everything in the com.google namespace into base, except classes in com.google.foobar + * that will go to feature2. Class based mappings takes precedence over packages (since they are + * more specific): + * <pre> + * com.google.A:feature2 + * com.google.*:base + * </pre> + * Puts A into feature2, and all other classes from com.google into base. + * + * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a + * placement tool. + */ +@Keep +public final class FeatureClassMapping { + + Map<String, String> parsedRules = new HashMap<>(); // Already parsed rules. + Map<String, String> parseNonClassRules = new HashMap<>(); + boolean usesOnlyExactMappings = true; + + Set<FeaturePredicate> mappings = new HashSet<>(); + + Path mappingFile; + String baseName = DEFAULT_BASE_NAME; + + static final String DEFAULT_BASE_NAME = "base"; + + static final String COMMENT = "#"; + static final String SEPARATOR = ":"; + + public String getBaseName() { + return baseName; + } + + private static class SpecificationOrigin extends PathOrigin { + + public SpecificationOrigin(Path path) { + super(path); + } + + @Override + public String part() { + return "specification file '" + super.part() + "'"; + } + } + + private static class JarFileOrigin extends PathOrigin { + + public JarFileOrigin(Path path) { + super(path); + } + + @Override + public String part() { + return "jar file '" + super.part() + "'"; + } + } + + public static FeatureClassMapping fromSpecification(Path file) throws FeatureMappingException { + return fromSpecification(file, new DiagnosticsHandler() {}); + } + + public static FeatureClassMapping fromSpecification(Path file, DiagnosticsHandler reporter) + throws FeatureMappingException { + FeatureClassMapping mapping = new FeatureClassMapping(); + List<String> lines = null; + try { + lines = FileUtils.readAllLines(file); + } catch (IOException e) { + ExceptionDiagnostic error = new ExceptionDiagnostic(e, new SpecificationOrigin(file)); + reporter.error(error); + throw new AbortException(error); + } + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + mapping.parseAndAdd(line, i); + } + return mapping; + } + + public static class Internal { + private static List<String> getClassFileDescriptors(String jar, DiagnosticsHandler reporter) { + Path jarPath = Paths.get(jar); + try { + return new ArchiveClassFileProvider(jarPath).getClassDescriptors() + .stream() + .map(DescriptorUtils::descriptorToJavaType) + .collect(Collectors.toList()); + } catch (IOException e) { + ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(jarPath)); + reporter.error(error); + throw new AbortException(error); + } + } + + private static List<String> getNonClassFiles(String jar, DiagnosticsHandler reporter) { + try (ZipFile zipfile = new ZipFile(jar, StandardCharsets.UTF_8)) { + return zipfile.stream() + .filter(entry -> !ZipUtils.isClassFile(entry.getName())) + .map(ZipEntry::getName) + .collect(Collectors.toList()); + } catch (IOException e) { + ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(Paths.get(jar))); + reporter.error(error); + throw new AbortException(error); + } + } + + public static FeatureClassMapping fromJarFiles( + List<FeatureJar> featureJars, List<String> baseJars, String baseName, + DiagnosticsHandler reporter) + throws FeatureMappingException { + FeatureClassMapping mapping = new FeatureClassMapping(); + if (baseName != null) { + mapping.baseName = baseName; + } + for (FeatureJar featureJar : featureJars) { + for (String javaType : getClassFileDescriptors(featureJar.getJar(), reporter)) { + mapping.addMapping(javaType, featureJar.getOutputName()); + } + for (String nonClass : getNonClassFiles(featureJar.getJar(), reporter)) { + mapping.addNonClassMapping(nonClass, featureJar.getOutputName()); + } + } + for (String baseJar : baseJars) { + for (String javaType : getClassFileDescriptors(baseJar, reporter)) { + mapping.addBaseMapping(javaType); + } + for (String nonClass : getNonClassFiles(baseJar, reporter)) { + mapping.addBaseNonClassMapping(nonClass); + } + } + assert mapping.usesOnlyExactMappings; + return mapping; + } + + } + + private FeatureClassMapping() {} + + public void addBaseMapping(String clazz) throws FeatureMappingException { + addMapping(clazz, baseName); + } + + public void addBaseNonClassMapping(String name) { + addNonClassMapping(name, baseName); + } + + public void addMapping(String clazz, String feature) throws FeatureMappingException { + addRule(clazz, feature, 0); + } + + public void addNonClassMapping(String name, String feature) { + // If a non-class file is present in multiple features put the resource in the base. + parseNonClassRules.put(name, parseNonClassRules.containsKey(name) ? baseName : feature); + } + + FeatureClassMapping(List<String> lines) throws FeatureMappingException { + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + parseAndAdd(line, i); + } + } + + public String featureForClass(String clazz) { + if (usesOnlyExactMappings) { + return parsedRules.getOrDefault(clazz, baseName); + } else { + FeaturePredicate bestMatch = null; + for (FeaturePredicate mapping : mappings) { + if (mapping.match(clazz)) { + if (bestMatch == null || bestMatch.predicate.length() < mapping.predicate.length()) { + bestMatch = mapping; + } + } + } + if (bestMatch == null) { + return baseName; + } + return bestMatch.feature; + } + } + + public String featureForNonClass(String nonClass) { + return parseNonClassRules.getOrDefault(nonClass, baseName); + } + + private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException { + if (line.startsWith(COMMENT)) { + return; // Ignore comments + } + if (line.isEmpty()) { + return; // Ignore blank lines + } + + if (!line.contains(SEPARATOR)) { + error("Mapping lines must contain a " + SEPARATOR, lineNumber); + } + String[] values = line.split(SEPARATOR); + if (values.length != 2) { + error("Mapping lines can only contain one " + SEPARATOR, lineNumber); + } + + String predicate = values[0]; + String feature = values[1]; + addRule(predicate, feature, lineNumber); + } + + private void addRule(String predicate, String feature, int lineNumber) + throws FeatureMappingException { + if (parsedRules.containsKey(predicate)) { + if (!parsedRules.get(predicate).equals(feature)) { + error("Redefinition of predicate " + predicate + "not allowed", lineNumber); + } + return; // Already have this rule. + } + parsedRules.put(predicate, feature); + FeaturePredicate featurePredicate = new FeaturePredicate(predicate, feature); + mappings.add(featurePredicate); + usesOnlyExactMappings &= featurePredicate.isExactmapping(); + } + + private void error(String error, int line) throws FeatureMappingException { + throw new FeatureMappingException( + "Invalid mappings specification: " + error + "\n in file " + mappingFile + ":" + line); + } + + @Keep + public static class FeatureMappingException extends Exception { + FeatureMappingException(String message) { + super(message); + } + } + + /** A feature predicate can either be a wildcard or class predicate. */ + private static class FeaturePredicate { + private static Pattern identifier = Pattern.compile("[A-Za-z_\\-][A-Za-z0-9_$\\-]*"); + final String predicate; + final String feature; + final boolean isCatchAll; + // False implies class predicate. + final boolean isWildcard; + + FeaturePredicate(String predicate, String feature) throws FeatureMappingException { + isWildcard = predicate.endsWith(".*"); + isCatchAll = predicate.equals("*"); + if (isCatchAll) { + this.predicate = ""; + } else if (isWildcard) { + String packageName = predicate.substring(0, predicate.length() - 2); + if (!DescriptorUtils.isValidJavaType(packageName)) { + throw new FeatureMappingException(packageName + " is not a valid identifier"); + } + // Prefix of a fully-qualified class name, including a terminating dot. + this.predicate = predicate.substring(0, predicate.length() - 1); + } else { + if (!DescriptorUtils.isValidJavaType(predicate)) { + throw new FeatureMappingException(predicate + " is not a valid identifier"); + } + this.predicate = predicate; + } + this.feature = feature; + } + + boolean match(String className) { + if (isCatchAll) { + return true; + } else if (isWildcard) { + return className.startsWith(predicate); + } else { + return className.equals(predicate); + } + } + + boolean isExactmapping() { + return !isWildcard && !isCatchAll; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index a2c6aae..2d401df 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1878,6 +1878,8 @@ public boolean readInputStackMaps = true; public boolean disableStackMapVerification = false; + public boolean disableShortenLiveRanges = false; + // Force each call of application read to dump its inputs to a file, which is subsequently // deleted. Useful to check that our dump functionality does not cause compilation failure. public boolean dumpAll = false;
diff --git a/src/main/keep.txt b/src/main/keep.txt index deb8e6d..e376924 100644 --- a/src/main/keep.txt +++ b/src/main/keep.txt
@@ -12,6 +12,7 @@ -keep public class com.android.tools.r8.D8 { public static void main(java.lang.String[]); } -keep public class com.android.tools.r8.R8 { public static void main(java.lang.String[]); } -keep public class com.android.tools.r8.ExtractMarker { public static void main(java.lang.String[]); } +-keep public class com.android.tools.r8.dexsplitter.DexSplitter { public static void main(java.lang.String[]); } -keep public class com.android.tools.r8.Version { public static final java.lang.String LABEL; } -keep public class com.android.tools.r8.Version { public static java.lang.String getVersionString(); }
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java index c4d8683..52d84fc 100644 --- a/src/test/java/com/android/tools/r8/TestBuilder.java +++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -249,4 +249,9 @@ getState().setDiagnosticsLevelModifier(modifier); return self(); } + + public T allowStdoutMessages() { + // Default ignored. + return self(); + } }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java index b315027..ead3195 100644 --- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java +++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -461,6 +461,7 @@ return super.addLibraryProvider(provider); } + @Override public T allowStdoutMessages() { allowStdoutMessages = true; return self();
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java index 48e94f0..cf33980 100644 --- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java +++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -93,6 +93,10 @@ return withCfRuntime(CfRuntime.getDefaultCfRuntime().getVm()); } + public TestParametersBuilder withDefaultRuntimes() { + return withDefaultDexRuntime().withDefaultCfRuntime(); + } + /** Add all available CF runtimes. */ public TestParametersBuilder withCfRuntimes() { return withCfRuntimeFilter(vm -> true);
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java index 5445d03..a5c5302 100644 --- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java +++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -332,6 +332,16 @@ return self(); } + public T addKeepEnumsRule() { + addKeepRules( + StringUtils.lines( + "-keepclassmembers enum * {", + " public static **[] values();", + " public static ** valueOf(java.lang.String);", + "}")); + return self(); + } + public T addPrintSeeds() { return addKeepRules("-printseeds"); }
diff --git a/src/test/java/com/android/tools/r8/annotations/DefaultAnnotationTest.java b/src/test/java/com/android/tools/r8/annotations/DefaultAnnotationTest.java new file mode 100644 index 0000000..a35b813 --- /dev/null +++ b/src/test/java/com/android/tools/r8/annotations/DefaultAnnotationTest.java
@@ -0,0 +1,101 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.annotations; + +import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ThrowableConsumer; +import com.android.tools.r8.utils.StringUtils; +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class DefaultAnnotationTest extends TestBase { + + static final String ANNO_NAME = typeName(MyAnnotation.class); + static final String EXPECTED = StringUtils.lines("@" + ANNO_NAME + "(hello=Hello World!)"); + static final String EXPECTED_QUOTES = + StringUtils.lines("@" + ANNO_NAME + "(hello=\"Hello World!\")"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().withAllApiLevels().build(); + } + + public DefaultAnnotationTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testNoDefinition() throws Exception { + runTest(b -> {}); + } + + @Test + public void testOnLibrary() throws Exception { + runTest(b -> b.addLibraryClassFileData(getDalvikAnnotationDefault())); + } + + @Test + public void testOnProgram() throws Exception { + runTest(b -> b.addProgramClassFileData(getDalvikAnnotationDefault())); + } + + private void runTest(ThrowableConsumer<R8FullTestBuilder> modification) throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(MyAnnotation.class, MyAnnotatedClass.class, TestClass.class) + .addDefaultRuntimeLibrary(parameters) + .apply(modification) + .addKeepAllAttributes() + .addKeepRules("-keep class * { *; }") + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(getExpected()); + } + + private String getExpected() { + if (parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK9)) { + return EXPECTED_QUOTES; + } + return EXPECTED; + } + + private static byte[] getDalvikAnnotationDefault() throws Exception { + return transformer(WillBeDalvikAnnotationAnnotationDefault.class) + .setClassDescriptor("Ldalvik/annotation/AnnotationDefault;") + .transform(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface WillBeDalvikAnnotationAnnotationDefault {} + + @Retention(RetentionPolicy.RUNTIME) + @interface MyAnnotation { + String hello() default "Hello World!"; + } + + @MyAnnotation + static class MyAnnotatedClass {} + + static class TestClass { + + public static void main(String[] args) { + for (Annotation annotation : MyAnnotatedClass.class.getAnnotations()) { + System.out.println(annotation.toString()); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java index ccec2d8..ac4e8d8 100644 --- a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java +++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
@@ -4,11 +4,11 @@ package com.android.tools.r8.classmerging.horizontal; -import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; import static org.hamcrest.MatcherAssert.assertThat; import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NoHorizontalClassMerging; import com.android.tools.r8.TestParameters; import com.android.tools.r8.utils.codeinspector.ClassSubject; import org.junit.Test; @@ -24,6 +24,7 @@ .addInnerClasses(getClass()) .addKeepMainRule(Main.class) .enableNeverClassInliningAnnotations() + .enableNoHorizontalClassMergingAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), Main.class) .assertSuccessWithOutputLines("b: main", "true") @@ -31,18 +32,20 @@ codeInspector -> { ClassSubject aClassSubject = codeInspector.clazz(A.class); assertThat(aClassSubject, isPresent()); - assertThat(aClassSubject.init(), isPresent()); - assertThat(codeInspector.clazz(A.class), isPresent()); - assertThat(codeInspector.clazz(B.class), isAbsent()); + ClassSubject bClassSubject = codeInspector.clazz(B.class); + assertThat(bClassSubject, isPresent()); + assertThat(bClassSubject.init(), isPresent()); }); } @NeverClassInline + @NoHorizontalClassMerging public static class A {} @NeverClassInline + @NoHorizontalClassMerging public static class B { public B(String v) { System.out.println("b: " + v); @@ -50,7 +53,7 @@ } public static class Main { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { new B("main"); System.out.println(A.class.toString().length() > 0); }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CanonicalConstantInPreambleTest.java b/src/test/java/com/android/tools/r8/debuginfo/CanonicalConstantInPreambleTest.java new file mode 100644 index 0000000..ab97b85 --- /dev/null +++ b/src/test/java/com/android/tools/r8/debuginfo/CanonicalConstantInPreambleTest.java
@@ -0,0 +1,101 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.debuginfo; + +import com.android.tools.r8.D8TestBuilder; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class CanonicalConstantInPreambleTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("Hello, world"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build(); + } + + public CanonicalConstantInPreambleTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForRuntime(parameters) + .addProgramClassFileData(getTransformedTestClass()) + .apply( + b -> { + // Disable shorten live ranges to ensure the canonicalized constant remains in + // preamble. + if (b instanceof D8TestBuilder) { + ((D8TestBuilder) b) + .addOptionsModification(o -> o.testing.disableShortenLiveRanges = true); + } + }) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + private byte[] getTransformedTestClass() throws Exception { + return transformer(TestClass.class) + .stripFrames("foo") + .setVersion(CfVersion.V1_5) + .removeLineNumberTable(MethodPredicate.onName("foo")) + .transformMethodInsnInMethod( + "foo", + (opcode, owner, name, descriptor, isInterface, visitor) -> { + if (!name.equals("nanoTime")) { + visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + return; + } + // Jump over the preamble code. + Label postPreamble = new Label(); + visitor.visitJumpInsn(Opcodes.GOTO, postPreamble); + visitor.visitLabel(postPreamble); + visitor.visitInsn(Opcodes.LCONST_0); + // Start an actual line after the preamble instructions. + Label firstLine = new Label(); + visitor.visitLabel(firstLine); + visitor.visitLineNumber(42, firstLine); + // Create a trivial block that just has the active line and falls through. + // This is the block that we expect to have been removed, but due to the constant + // being moved up and having the wrong line it will fail. + Label trivialFallthrough = new Label(); + visitor.visitJumpInsn(Opcodes.GOTO, trivialFallthrough); + visitor.visitLabel(trivialFallthrough); + }) + .transform(); + } + + static class TestClass { + + public static void foo(String arg) { + System.nanoTime(); // Will be replaced by transformed to preamble code. + // The constant '1' will be canonicalized and inserted in the preamble. + if (arg.equals("0") || arg.equals("1")) { + System.out.print("Hello, "); + } + if (!arg.equals("1")) { + System.out.println("world"); + } + } + + public static void main(String[] args) { + foo(String.valueOf(args.length)); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NopLineRegression230337727Test.java b/src/test/java/com/android/tools/r8/debuginfo/NopLineRegression230337727Test.java new file mode 100644 index 0000000..ccdac2d --- /dev/null +++ b/src/test/java/com/android/tools/r8/debuginfo/NopLineRegression230337727Test.java
@@ -0,0 +1,141 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.debuginfo; + +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.cf.CfVersion; +import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import java.util.Arrays; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class NopLineRegression230337727Test extends TestBase implements Opcodes { + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build(); + } + + private final TestParameters parameters; + + public NopLineRegression230337727Test(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + testForRuntime(parameters) + .addProgramClassFileData(getTestClassTransformed()) + .run(parameters.getRuntime(), TestClass.class) + .inspect( + inspector -> { + MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithName("foo"); + assertTrue( + foo.getLineNumberTable().getLines().toString(), + foo.getLineNumberTable().getLines().contains(11)); + }); + } + + static class TestClass { + + public static boolean boo() { + return true; + } + + public static void foo(List<Integer> args) { + System.nanoTime(); + // Code added by transformer for the kotlin code: + // if (boo()) { + // l.forEach {} + // } + // if (boo()) {} + } + + public static void main(String[] args) { + foo(Arrays.asList(1, 2, 3)); + } + } + + private static byte[] getTestClassTransformed() throws Exception { + // Code generated by kotlin but with frames, locals and checkNotNullParameter removed. + String holder = binaryName(TestClass.class); + return transformer(TestClass.class) + .setVersion(CfVersion.V1_5) // Avoid need of stack frames + .setMaxs(MethodPredicate.onName("foo"), 2, 7) + .transformMethodInsnInMethod( + "foo", + (opcode, owner, name, descriptor, isInterface, visitor) -> { + Label label1 = new Label(); + visitor.visitLabel(label1); + visitor.visitLineNumber(4, label1); + visitor.visitMethodInsn(INVOKESTATIC, holder, "boo", "()Z", false); + Label label2 = new Label(); + visitor.visitJumpInsn(IFEQ, label2); + Label label3 = new Label(); + visitor.visitLabel(label3); + visitor.visitLineNumber(5, label3); + visitor.visitVarInsn(ALOAD, 0); + visitor.visitTypeInsn(CHECKCAST, "java/lang/Iterable"); + visitor.visitVarInsn(ASTORE, 1); + visitor.visitInsn(ICONST_0); + visitor.visitVarInsn(ISTORE, 2); + Label label5 = new Label(); + visitor.visitLabel(label5); + visitor.visitLineNumber(10, label5); + visitor.visitVarInsn(ALOAD, 1); + visitor.visitMethodInsn( + INVOKEINTERFACE, + "java/lang/Iterable", + "iterator", + "()Ljava/util/Iterator;", + true); + visitor.visitVarInsn(ASTORE, 3); + Label label6 = new Label(); + visitor.visitLabel(label6); + visitor.visitVarInsn(ALOAD, 3); + visitor.visitMethodInsn( + INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true); + Label label7 = new Label(); + visitor.visitJumpInsn(IFEQ, label7); + visitor.visitVarInsn(ALOAD, 3); + visitor.visitMethodInsn( + INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true); + visitor.visitVarInsn(ASTORE, 4); + visitor.visitVarInsn(ALOAD, 4); + visitor.visitTypeInsn(CHECKCAST, "java/lang/Number"); + visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I", false); + visitor.visitVarInsn(ISTORE, 5); + visitor.visitInsn(ICONST_0); + visitor.visitVarInsn(ISTORE, 6); + Label label10 = new Label(); + visitor.visitLabel(label10); + visitor.visitLineNumber(5, label10); + visitor.visitInsn(NOP); + visitor.visitJumpInsn(GOTO, label6); + visitor.visitLabel(label7); + visitor.visitLineNumber(11, label7); + visitor.visitInsn(NOP); + visitor.visitLabel(label2); + visitor.visitLineNumber(7, label2); + visitor.visitMethodInsn(INVOKESTATIC, holder, "boo", "()Z", false); + Label label12 = new Label(); + visitor.visitJumpInsn(IFEQ, label12); + visitor.visitLabel(label12); + visitor.visitLineNumber(8, label12); + visitor.visitInsn(RETURN); + }) + .transform(); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java new file mode 100644 index 0000000..3c1716a --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
@@ -0,0 +1,164 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.desugar.desugaredlibrary; + +import com.android.tools.r8.LibraryDesugaringTestConfiguration; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.ZipUtils.ZipBuilder; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Function; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +// Test of an API which is to be treated as a platform API. This is simulating an API which can be +// implemented by an OEM in an on-device APK containing the DEX code. The program will probe for the +// OEM class and if not found it will use a built-in program class implementing the interface. +// +// This is tested with and without library desugaring. + +// For context see b/229793269. +@RunWith(Parameterized.class) +public class PseudoPlatformApiTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters() + .withDexRuntimes() + .withApiLevelsStartingAtIncluding(AndroidApiLevel.P) + .build()); + } + + private Path androidJarAdditions() throws Exception { + return ZipBuilder.builder(temp.newFile("additions_android.jar").toPath()) + .addFilesRelative( + ToolHelper.getClassPathForTests(), + ToolHelper.getClassFileForTestClass(PseudoPlatformInterface.class)) + .build(); + } + + private Path androidJarAdditionsDex() throws Exception { + // Build the OEM DEX files without library desugaring enabled and using min API 28. + return testForD8(parameters.getBackend()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addProgramClasses(PseudoPlatformInterface.class) + .setMinApi(AndroidApiLevel.B) + .compile() + .writeToZip(); + } + + private Path oemDex() throws Exception { + // Build the OEM DEX files without library desugaring enabled and using min API 28. + return testForD8(parameters.getBackend()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + // This is equivalent to adding the PseudoPlatformInterface to android.jar. + .addLibraryClasses(PseudoPlatformInterface.class) + .addProgramClasses(OemClass.class) + .setMinApi(AndroidApiLevel.P) + .compile() + .writeToZip(); + } + + @Test + public void testD8WithoutLibraryDesugaringOemClassNotPresent() throws Exception { + testForD8(parameters.getBackend()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addLibraryFiles(androidJarAdditions()) + .addProgramClasses(ProgramClass.class) + .setMinApi(AndroidApiLevel.H_MR2) + .addRunClasspathFiles(androidJarAdditionsDex()) + .run(parameters.getRuntime(), ProgramClass.class) + .assertSuccessWithOutputLines("DEFAULT-X", "Y-DEFAULT"); + } + + @Test + public void testD8WithoutLibraryDesugaringOemClassPresent() throws Exception { + testForD8(parameters.getBackend()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addLibraryFiles(androidJarAdditions()) + .addProgramClasses(ProgramClass.class) + .setMinApi(AndroidApiLevel.H_MR2) + .addRunClasspathFiles(androidJarAdditionsDex()) + .addRunClasspathFiles(oemDex()) + .run(parameters.getRuntime(), ProgramClass.class) + .assertSuccessWithOutputLines("OEM-X", "Y-OEM"); + } + + @Test + public void testD8WithLibraryDesugaringOemClassNotPresent() throws Exception { + testForD8(parameters.getBackend()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addLibraryFiles(androidJarAdditions()) + .addProgramClasses(ProgramClass.class) + .setMinApi(AndroidApiLevel.H_MR2) + // Enable library desugaring with an effective min API of 1. + .enableLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B)) + .addRunClasspathFiles(androidJarAdditionsDex()) + .run(parameters.getRuntime(), ProgramClass.class) + .assertSuccessWithOutputLines("DEFAULT-X", "Y-DEFAULT"); + } + + @Test + public void testD8WithLibraryDesugaringOemClassPresent() throws Exception { + testForD8(parameters.getBackend()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addLibraryFiles(androidJarAdditions()) + .addProgramClasses(ProgramClass.class) + .setMinApi(AndroidApiLevel.H_MR2) + // Enable library desugaring with an effective min API of 1. + .enableLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B)) + .addRunClasspathFiles(androidJarAdditionsDex()) + .addRunClasspathFiles(oemDex()) + .run(parameters.getRuntime(), ProgramClass.class) + .assertSuccessWithOutputLines("OEM-X", "Y-OEM"); + } + + interface PseudoPlatformInterface { + Function<String, String> api(Function<String, String> fn); + } + + static class OemClass implements PseudoPlatformInterface { + public Function<String, String> api(Function<String, String> fn) { + System.out.println(fn.apply("OEM-")); + return (v) -> v + "-OEM"; + } + } + + static class ProgramClass implements PseudoPlatformInterface { + public Function<String, String> api(Function<String, String> fn) { + System.out.println(fn.apply("DEFAULT-")); + return (v) -> v + "-DEFAULT"; + } + + public static void main(String[] args) { + PseudoPlatformInterface pseudoPlatformInterface = null; + try { + Class<?> oemClass = + Class.forName( + "com.android.tools.r8.desugar.desugaredlibrary.PseudoPlatformApiTest$OemClass"); + pseudoPlatformInterface = + (PseudoPlatformInterface) oemClass.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException + | NoSuchMethodException + | InstantiationException + | InvocationTargetException + | IllegalAccessException e) { + // Could not load OEM class use built-in program class. + pseudoPlatformInterface = new ProgramClass(); + } + Function<String, String> fn = (v) -> v + "X"; + System.out.println(pseudoPlatformInterface.api(fn).apply("Y")); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java index 7b78157..effd169 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
@@ -16,15 +16,20 @@ import com.android.tools.r8.utils.StringUtils; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +45,19 @@ "String written: Hello World", "bytes read: 11", "String read: Hello World", + "bytes read: 11", + "String read: Hello World", + "null", + "true", + "unsupported"); + private static final String EXPECTED_RESULT_24_26 = + StringUtils.lines( + "bytes written: 11", + "String written: Hello World", + "bytes read: 11", + "String read: Hello World", + "unsupported", + "unsupported", "null", "true", "unsupported"); @@ -49,6 +67,8 @@ "String written: Hello World", "bytes read: 11", "String read: Hello World", + "bytes read: 11", + "String read: Hello World", "true", "true", "true"); @@ -69,8 +89,11 @@ } private String getExpectedResult() { - return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O) - ? EXPECTED_RESULT_26 + if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)) { + return EXPECTED_RESULT_26; + } + return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N) + ? EXPECTED_RESULT_24_26 : EXPECTED_RESULT; } @@ -124,8 +147,12 @@ public static void main(String[] args) throws Throwable { Path path = Files.createTempFile("example", ".txt"); - readWrite(path); + readWriteThroughFilesAPI(path); + readThroughFileChannelAPI(path); + attributeAccess(path); + } + private static void attributeAccess(Path path) throws IOException { PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class); if (view != null) { System.out.println( @@ -154,7 +181,7 @@ } } - private static void readWrite(Path path) throws IOException { + private static void readWriteThroughFilesAPI(Path path) throws IOException { try (SeekableByteChannel channel = Files.newByteChannel(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) { String toWrite = "Hello World"; @@ -173,5 +200,26 @@ System.out.println("String read: " + new String(byteBuffer2.array())); } } + + private static void readThroughFileChannelAPI(Path path) throws IOException { + try { + Set<OpenOption> openOptions = new HashSet<>(); + openOptions.add(LinkOption.NOFOLLOW_LINKS); + try (FileChannel channel = FileChannel.open(path, openOptions)) { + String toWrite = "Hello World"; + + // Read the String toWrite from the channel. + channel.position(0); + ByteBuffer byteBuffer2 = ByteBuffer.allocate(toWrite.length()); + int read = channel.read(byteBuffer2); + System.out.println("bytes read: " + read); + System.out.println("String read: " + new String(byteBuffer2.array())); + } + } catch (NoClassDefFoundError err) { + // TODO(b/222647019): FileChannel#open is not supported in between 24 and 26. + System.out.println("unsupported"); + System.out.println("unsupported"); + } + } } }
diff --git a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564InvalidCode.java b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564InvalidCode.java new file mode 100644 index 0000000..ddd3386 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564InvalidCode.java
@@ -0,0 +1,107 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.jdk8272564; + +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.DescriptorUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.Opcodes; + +@RunWith(Parameterized.class) +public class Jdk8272564InvalidCode extends TestBase { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters() + .withCfRuntimes() + .withDexRuntimes() + .withAllApiLevelsAlsoForCf() + .build(); + } + + @Parameter(0) + public TestParameters parameters; + + private boolean isDefaultCfParameters() { + return parameters.isCfRuntime() && parameters.getApiLevel().equals(AndroidApiLevel.B); + } + + @Test + public void testRuntime() throws Exception { + assumeTrue(isDefaultCfParameters()); + testForJvm() + .addProgramClasses(I.class) + .addProgramClassFileData(getTransformedClass()) + .run(parameters.getRuntime(), A.class) + .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class); + } + + @Test + public void testDesugaring() throws Exception { + assumeTrue(parameters.isDexRuntime() || isDefaultCfParameters()); + testForDesugaring(parameters) + .addProgramClasses(I.class) + .addProgramClassFileData(getTransformedClass()) + .run(parameters.getRuntime(), A.class) + .applyIf( + parameters.isDexRuntime() + && parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V4_4_4), + r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class), + r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class)); + } + + @Test + public void testR8() throws Exception { + assumeTrue(parameters.isDexRuntime() || isDefaultCfParameters()); + // The R8 lens code rewriter rewrites to the code prior to fixing JDK-8272564. + testForR8(parameters.getBackend()) + .addProgramClasses(I.class) + .addProgramClassFileData(getTransformedClass()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(A.class) + .addOptionsModification(options -> options.testing.allowInvokeErrors = true) + .run(parameters.getRuntime(), A.class) + .applyIf( + parameters.isDexRuntime() + && parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V4_4_4), + r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class), + r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class)); + } + + private byte[] getTransformedClass() throws Exception { + return transformer(A.class) + .transformMethodInsnInMethod( + "main", + ((opcode, owner, name, descriptor, isInterface, continuation) -> { + continuation.visitMethodInsn( + name.equals("hashCode") ? Opcodes.INVOKEINTERFACE : opcode, + // javac generates java.lang.object as holder, change it to A. + name.equals("hashCode") ? DescriptorUtils.getClassBinaryName(A.class) : owner, + name, + descriptor, + name.equals("hashCode") || isInterface); + })) + .transform(); + } + + interface I {} + + static class A implements I { + + public static void main(String[] args) { + new A().hashCode(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java index 71e7df5..45a3be4 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
@@ -47,6 +47,28 @@ } @Test + public void testPropagationFromFeature() throws Exception { + ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone = + r8TestCompileResult -> { + // Ensure that getFromFeature from FeatureClass is inlined into the run method. + ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class); + assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent())); + }; + ProcessResult processResult = + testDexSplitter( + parameters, + ImmutableSet.of(BaseSuperClass.class), + ImmutableSet.of(FeatureClass.class), + FeatureClass.class, + EXPECTED, + ensureGetFromFeatureGone, + TestShrinkerBuilder::noMinification); + // We expect art to fail on this with the dex splitter, see b/122902374 + assertNotEquals(processResult.exitCode, 0); + assertTrue(processResult.stderr.contains("NoClassDefFoundError")); + } + + @Test public void testOnR8Splitter() throws IOException, CompilationFailedException { assumeTrue(parameters.isDexRuntime()); ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java index 53af2b6..96220f8 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -48,6 +48,31 @@ } @Test + public void testInliningFromFeature() throws Exception { + ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone = + r8TestCompileResult -> { + // Ensure that getFromFeature from FeatureClass is inlined into the run method. + ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class); + assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent())); + }; + Consumer<R8FullTestBuilder> configurator = + r8FullTestBuilder -> + r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification(); + ProcessResult processResult = + testDexSplitter( + parameters, + ImmutableSet.of(BaseSuperClass.class), + ImmutableSet.of(FeatureClass.class), + FeatureClass.class, + EXPECTED, + ensureGetFromFeatureGone, + configurator); + // We expect art to fail on this with the dex splitter, see b/122902374 + assertNotEquals(processResult.exitCode, 0); + assertTrue(processResult.stderr.contains("NoClassDefFoundError")); + } + + @Test public void testOnR8Splitter() throws IOException, CompilationFailedException { assumeTrue(parameters.isDexRuntime()); ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java index a2e1b1d..38d19da 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -46,6 +46,28 @@ } @Test + public void testPropagationFromFeature() throws Exception { + ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone = + r8TestCompileResult -> { + // Ensure that getFromFeature from FeatureClass is inlined into the run method. + ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class); + assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent())); + }; + ProcessResult processResult = + testDexSplitter( + parameters, + ImmutableSet.of(BaseSuperClass.class), + ImmutableSet.of(FeatureClass.class, FeatureEnum.class), + FeatureClass.class, + EXPECTED, + ensureGetFromFeatureGone, + builder -> builder.enableInliningAnnotations().noMinification()); + // We expect art to fail on this with the dex splitter, see b/122902374 + assertNotEquals(processResult.exitCode, 0); + assertTrue(processResult.stderr.contains("NoClassDefFoundError")); + } + + @Test public void testOnR8Splitter() throws IOException, CompilationFailedException { assumeTrue(parameters.isDexRuntime()); ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java index a7d137f..71ddab8 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -48,6 +48,43 @@ } @Test + public void testInliningFromFeature() throws Exception { + // Static merging is based on sorting order, we assert that we merged to the feature. + ThrowingConsumer<R8TestCompileResult, Exception> ensureMergingToFeature = + r8TestCompileResult -> { + ClassSubject clazz = r8TestCompileResult.inspector().clazz(AFeatureWithStatic.class); + assertEquals(2, clazz.allMethods().size()); + assertThat(clazz.uniqueMethodWithName("getBase42"), isPresent()); + assertThat(clazz.uniqueMethodWithName("getFoobar"), isPresent()); + }; + Consumer<R8FullTestBuilder> configurator = + r8FullTestBuilder -> + r8FullTestBuilder + .addOptionsModification( + options -> + options.testing.horizontalClassMergingTarget = + (appView, candidates, target) -> candidates.iterator().next()) + .addHorizontallyMergedClassesInspector( + inspector -> + inspector.assertMergedInto(BaseWithStatic.class, AFeatureWithStatic.class)) + .enableNoVerticalClassMergingAnnotations() + .enableInliningAnnotations() + .noMinification(); + ProcessResult processResult = + testDexSplitter( + parameters, + ImmutableSet.of(BaseClass.class, BaseWithStatic.class), + ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class), + FeatureClass.class, + EXPECTED, + ensureMergingToFeature, + configurator); + // We expect art to fail on this with the dex splitter. + assertNotEquals(processResult.exitCode, 0); + assertTrue(processResult.stderr.contains("NoClassDefFoundError")); + } + + @Test public void testOnR8Splitter() throws IOException, CompilationFailedException { assumeTrue(parameters.isDexRuntime()); ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java new file mode 100644 index 0000000..e90e0ab --- /dev/null +++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -0,0 +1,484 @@ +// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.dexsplitter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.D8Command; +import com.android.tools.r8.DexSplitterHelper; +import com.android.tools.r8.ExtractMarker; +import com.android.tools.r8.OutputMode; +import com.android.tools.r8.R8; +import com.android.tools.r8.R8Command; +import com.android.tools.r8.ResourceException; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ToolHelper.ArtCommandBuilder; +import com.android.tools.r8.dex.Marker; +import com.android.tools.r8.dexsplitter.DexSplitter.Options; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class DexSplitterTests { + + private static final String CLASS_DIR = + ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR + "classes/dexsplitsample"; + private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class"; + private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class"; + private static final String CLASS3_CLASS = CLASS_DIR + "/Class3.class"; + private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class"; + private static final String CLASS3_SYNTHETIC_CLASS = CLASS_DIR + "/Class3$1.class"; + private static final String CLASS4_CLASS = CLASS_DIR + "/Class4.class"; + private static final String CLASS4_LAMBDA_INTERFACE = CLASS_DIR + "/Class4$LambdaInterface.class"; + private static final String TEXT_FILE = + ToolHelper.EXAMPLES_ANDROID_N_DIR + "dexsplitsample/TextFile.txt"; + + + @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); + + private Path createInput(boolean dontCreateMarkerInD8) + throws IOException, CompilationFailedException { + // Initial normal compile to create dex files. + Path inputZip = temp.newFolder().toPath().resolve("input.zip"); + D8Command command = + D8Command.builder() + .setOutput(inputZip, OutputMode.DexIndexed) + .addProgramFiles(Paths.get(CLASS1_CLASS)) + .addProgramFiles(Paths.get(CLASS2_CLASS)) + .addProgramFiles(Paths.get(CLASS3_CLASS)) + .addProgramFiles(Paths.get(CLASS3_INNER_CLASS)) + .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS)) + .addProgramFiles(Paths.get(CLASS4_CLASS)) + .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE)) + .build(); + + DexSplitterHelper.runD8ForTesting(command, dontCreateMarkerInD8); + + return inputZip; + } + + private void testMarker(boolean addMarkerToInput) + throws CompilationFailedException, IOException, ResourceException, ExecutionException { + Path inputZip = createInput(!addMarkerToInput); + + Path output = temp.newFolder().toPath().resolve("output"); + Files.createDirectory(output); + Path splitSpec = createSplitSpec(); + + DexSplitter.main( + new String[] { + "--input", inputZip.toString(), + "--output", output.toString(), + "--feature-splits", splitSpec.toString() + }); + + Path base = output.resolve("base").resolve("classes.dex"); + Path feature = output.resolve("feature1").resolve("classes.dex"); + + for (Path path : new Path[] {inputZip, base, feature}) { + Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(path); + assertEquals(addMarkerToInput ? 1 : 0, markers.size()); + } + } + + @Test + public void testMarkerPreserved() + throws CompilationFailedException, IOException, ResourceException, ExecutionException { + testMarker(true); + } + + @Test + public void testMarkerNotAdded() + throws CompilationFailedException, IOException, ResourceException, ExecutionException { + testMarker(false); + } + + /** + * To test the file splitting we have 3 classes that we distribute like this: Class1 -> base + * Class2 -> feature1 Class3 -> feature1 + * + * <p>Class1 and Class2 works independently of each other, but Class3 extends Class1, and + * therefore can't run without the base being loaded. + */ + @Test + public void splitFilesNoObfuscation() + throws CompilationFailedException, IOException, FeatureMappingException { + noObfuscation(false); + noObfuscation(true); + } + + private void noObfuscation(boolean useOptions) + throws IOException, CompilationFailedException, FeatureMappingException { + Path inputZip = createInput(false); + Path output = temp.newFolder().toPath().resolve("output"); + Files.createDirectory(output); + Path splitSpec = createSplitSpec(); + + if (useOptions) { + Options options = new Options(); + options.addInputArchive(inputZip.toString()); + options.setFeatureSplitMapping(splitSpec.toString()); + options.setOutput(output.toString()); + DexSplitter.run(options); + } else { + DexSplitter.main( + new String[] { + "--input", inputZip.toString(), + "--output", output.toString(), + "--feature-splits", splitSpec.toString() + }); + } + + Path base = output.resolve("base").resolve("classes.dex"); + Path feature = output.resolve("feature1").resolve("classes.dex"); + validateUnobfuscatedOutput(base, feature); + } + + private void validateUnobfuscatedOutput(Path base, Path feature) throws IOException { + // Both classes should still work if we give all dex files to the system. + for (String className : new String[] {"Class1", "Class2", "Class3"}) { + ArtCommandBuilder builder = new ArtCommandBuilder(); + builder.appendClasspath(base.toString()); + builder.appendClasspath(feature.toString()); + builder.setMainClass("dexsplitsample." + className); + String out = ToolHelper.runArt(builder); + assertEquals(out, className + "\n"); + } + // Individual classes should also work from the individual files. + String className = "Class1"; + ArtCommandBuilder builder = new ArtCommandBuilder(); + builder.appendClasspath(base.toString()); + builder.setMainClass("dexsplitsample." + className); + String out = ToolHelper.runArt(builder); + assertEquals(out, className + "\n"); + + className = "Class2"; + builder = new ArtCommandBuilder(); + builder.appendClasspath(feature.toString()); + builder.setMainClass("dexsplitsample." + className); + out = ToolHelper.runArt(builder); + assertEquals(out, className + "\n"); + + className = "Class3"; + builder = new ArtCommandBuilder(); + builder.appendClasspath(feature.toString()); + builder.setMainClass("dexsplitsample." + className); + try { + ToolHelper.runArt(builder); + assertFalse(true); + } catch (AssertionError assertionError) { + // We expect this to throw since base is not in the path and Class3 depends on Class1 + } + + className = "Class4"; + builder = new ArtCommandBuilder(); + builder.appendClasspath(feature.toString()); + builder.setMainClass("dexsplitsample." + className); + try { + ToolHelper.runArt(builder); + assertFalse(true); + } catch (AssertionError assertionError) { + // We expect this to throw since base is not in the path and Class4 includes a lambda that + // would have been pushed to base. + } + } + + private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException { + Path splitSpec = temp.getRoot().toPath().resolve("split_spec"); + try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) { + out.write( + "dexsplitsample.Class1:base\n" + + "dexsplitsample.Class2:feature1\n" + + "dexsplitsample.Class3:feature1\n" + + "dexsplitsample.Class4:feature1"); + } + return splitSpec; + } + + private List<String> getProguardConf() { + return ImmutableList.of( + "-keep class dexsplitsample.Class3 {", + " public static void main(java.lang.String[]);", + "}"); + } + + @Test + public void splitFilesFromJar() + throws IOException, CompilationFailedException, FeatureMappingException { + for (boolean useOptions : new boolean[]{false, true}) { + for (boolean explicitBase: new boolean[]{false, true}) { + for (boolean renameBase: new boolean[]{false, true}) { + splitFromJars(useOptions, explicitBase, renameBase); + } + } + } + } + + private void splitFromJars(boolean useOptions, boolean explicitBase, boolean renameBase) + throws IOException, CompilationFailedException, FeatureMappingException { + Path inputZip = createInput(false); + Path output = temp.newFolder().toPath().resolve("output"); + Files.createDirectory(output); + Path baseJar = temp.getRoot().toPath().resolve("base.jar"); + Path featureJar = temp.getRoot().toPath().resolve("feature1.jar"); + ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar)); + String name = "dexsplitsample/Class1.class"; + baseStream.putNextEntry(new ZipEntry(name)); + baseStream.write(Files.readAllBytes(Paths.get(CLASS1_CLASS))); + baseStream.closeEntry(); + baseStream.close(); + + ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar)); + name = "dexsplitsample/Class2.class"; + featureStream.putNextEntry(new ZipEntry(name)); + featureStream.write(Files.readAllBytes(Paths.get(CLASS2_CLASS))); + featureStream.closeEntry(); + name = "dexsplitsample/Class3.class"; + featureStream.putNextEntry(new ZipEntry(name)); + featureStream.write(Files.readAllBytes(Paths.get(CLASS3_CLASS))); + featureStream.closeEntry(); + name = "dexsplitsample/Class3$InnerClass.class"; + featureStream.putNextEntry(new ZipEntry(name)); + featureStream.write(Files.readAllBytes(Paths.get(CLASS3_INNER_CLASS))); + featureStream.closeEntry(); + name = "dexsplitsample/Class3$1.class"; + featureStream.putNextEntry(new ZipEntry(name)); + featureStream.write(Files.readAllBytes(Paths.get(CLASS3_SYNTHETIC_CLASS))); + featureStream.closeEntry(); + name = "dexsplitsample/Class4"; + featureStream.putNextEntry(new ZipEntry(name)); + featureStream.write(Files.readAllBytes(Paths.get(CLASS4_CLASS))); + featureStream.closeEntry(); + name = "dexsplitsample/Class4$LambdaInterface"; + featureStream.putNextEntry(new ZipEntry(name)); + featureStream.write(Files.readAllBytes(Paths.get(CLASS4_LAMBDA_INTERFACE))); + featureStream.closeEntry(); + featureStream.close(); + // Make sure that we can pass in a name for the output. + String specificOutputName = "renamed"; + if (useOptions) { + Options options = new Options(); + options.addInputArchive(inputZip.toString()); + options.setOutput(output.toString()); + if (explicitBase) { + options.addBaseJar(baseJar.toString()); + } else if (renameBase){ + // Ensure that we can rename base (if people called a feature base) + options.setBaseOutputName("base_renamed"); + } + options.addFeatureJar(featureJar.toString(), specificOutputName); + DexSplitter.run(options); + } else { + List<String> args = Lists.newArrayList( + "--input", + inputZip.toString(), + "--output", + output.toString(), + "--feature-jar", + featureJar.toString().concat(":").concat(specificOutputName)); + if (explicitBase) { + args.add("--base-jar"); + args.add(baseJar.toString()); + } else if (renameBase) { + args.add("--base-output-name"); + args.add("base_renamed"); + } + + DexSplitter.main(args.toArray(StringUtils.EMPTY_ARRAY)); + } + String baseOutputName = explicitBase || !renameBase ? "base" : "base_renamed"; + Path base = output.resolve(baseOutputName).resolve("classes.dex"); + Path feature = output.resolve(specificOutputName).resolve("classes.dex");; + validateUnobfuscatedOutput(base, feature); + } + + @Test + public void splitFilesObfuscation() + throws CompilationFailedException, IOException, ExecutionException { + // Initial normal compile to create dex files. + Path inputDex = temp.newFolder().toPath().resolve("input.zip"); + Path proguardMap = temp.getRoot().toPath().resolve("proguard.map"); + + R8.run( + R8Command.builder() + .setOutput(inputDex, OutputMode.DexIndexed) + .addProgramFiles(Paths.get(CLASS1_CLASS)) + .addProgramFiles(Paths.get(CLASS2_CLASS)) + .addProgramFiles(Paths.get(CLASS3_CLASS)) + .addProgramFiles(Paths.get(CLASS3_INNER_CLASS)) + .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS)) + .addProgramFiles(Paths.get(CLASS4_CLASS)) + .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE)) + .addLibraryFiles(ToolHelper.getDefaultAndroidJar()) + .setProguardMapOutputPath(proguardMap) + .addProguardConfiguration(getProguardConf(), Origin.unknown()) + .build()); + + Path outputDex = temp.newFolder().toPath().resolve("output"); + Files.createDirectory(outputDex); + Path splitSpec = createSplitSpec(); + + DexSplitter.main( + new String[] { + "--input", inputDex.toString(), + "--output", outputDex.toString(), + "--feature-splits", splitSpec.toString(), + "--proguard-map", proguardMap.toString() + }); + + Path base = outputDex.resolve("base").resolve("classes.dex"); + Path feature = outputDex.resolve("feature1").resolve("classes.dex"); + String class3 = "dexsplitsample.Class3"; + // We should still be able to run the Class3 which we kept, it has a call to the obfuscated + // class1 which is in base. + ArtCommandBuilder builder = new ArtCommandBuilder(); + builder.appendClasspath(base.toString()); + builder.appendClasspath(feature.toString()); + builder.setMainClass(class3); + String out = ToolHelper.runArt(builder); + assertEquals(out, "Class3\n"); + + // Class1 should not be in the feature, it should still be in base. + builder = new ArtCommandBuilder(); + builder.appendClasspath(feature.toString()); + builder.setMainClass(class3); + try { + ToolHelper.runArt(builder); + assertFalse(true); + } catch (AssertionError assertionError) { + // We expect this to throw since base is not in the path and Class3 depends on Class1. + } + + // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been + // shaken away. + CodeInspector inspector = new CodeInspector(base, proguardMap); + ClassSubject subject = inspector.clazz("dexsplitsample.Class1"); + assertTrue(subject.isPresent()); + assertTrue(subject.isRenamed()); + } + + @Test + public void splitNonClassFiles() + throws CompilationFailedException, IOException, FeatureMappingException { + Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar"); + ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip)); + String name = "dexsplitsample/TextFile.txt"; + inputZipStream.putNextEntry(new ZipEntry(name)); + byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE)); + inputZipStream.write(fileBytes); + inputZipStream.closeEntry(); + name = "dexsplitsample/TextFile2.txt"; + inputZipStream.putNextEntry(new ZipEntry(name)); + inputZipStream.write(fileBytes); + inputZipStream.write(fileBytes); + inputZipStream.closeEntry(); + inputZipStream.close(); + Path output = temp.newFolder().toPath().resolve("output"); + Files.createDirectory(output); + Path baseJar = temp.getRoot().toPath().resolve("base.jar"); + Path featureJar = temp.getRoot().toPath().resolve("feature1.jar"); + ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar)); + name = "dexsplitsample/TextFile.txt"; + baseStream.putNextEntry(new ZipEntry(name)); + baseStream.write(fileBytes); + baseStream.closeEntry(); + baseStream.close(); + ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar)); + name = "dexsplitsample/TextFile2.txt"; + featureStream.putNextEntry(new ZipEntry(name)); + featureStream.write(fileBytes); + featureStream.write(fileBytes); + featureStream.closeEntry(); + featureStream.close(); + Options options = new Options(); + options.addInputArchive(inputZip.toString()); + options.setOutput(output.toString()); + options.addFeatureJar(baseJar.toString()); + options.addFeatureJar(featureJar.toString()); + options.setSplitNonClassResources(true); + DexSplitter.run(options); + Path baseDir = output.resolve("base"); + Path featureDir = output.resolve("feature1"); + byte[] contents = fileBytes; + byte[] contents2 = new byte[contents.length * 2]; + System.arraycopy(contents, 0, contents2, 0, contents.length); + System.arraycopy(contents, 0, contents2, contents.length, contents.length); + Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt"); + Path featureTextFile = featureDir.resolve("dexsplitsample/TextFile2.txt"); + assert Files.exists(baseTextFile); + assert Files.exists(featureTextFile); + assert Arrays.equals(Files.readAllBytes(baseTextFile), contents); + assert Arrays.equals(Files.readAllBytes(featureTextFile), contents2); + } + + @Test + public void splitDuplicateNonClassFiles() + throws IOException, CompilationFailedException, FeatureMappingException { + Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar"); + ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip)); + String name = "dexsplitsample/TextFile.txt"; + inputZipStream.putNextEntry(new ZipEntry(name)); + byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE)); + inputZipStream.write(fileBytes); + inputZipStream.closeEntry(); + inputZipStream.close(); + Path output = temp.newFolder().toPath().resolve("output"); + Files.createDirectory(output); + Path featureJar = temp.getRoot().toPath().resolve("feature1.jar"); + Path feature2Jar = temp.getRoot().toPath().resolve("feature2.jar"); + ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar)); + name = "dexsplitsample/TextFile.txt"; + featureStream.putNextEntry(new ZipEntry(name)); + featureStream.write(fileBytes); + featureStream.closeEntry(); + featureStream.close(); + ZipOutputStream feature2Stream = new ZipOutputStream(Files.newOutputStream(feature2Jar)); + name = "dexsplitsample/TextFile.txt"; + feature2Stream.putNextEntry(new ZipEntry(name)); + feature2Stream.write(fileBytes); + feature2Stream.closeEntry(); + feature2Stream.close(); + Options options = new Options(); + options.addInputArchive(inputZip.toString()); + options.setOutput(output.toString()); + options.addFeatureJar(feature2Jar.toString()); + options.addFeatureJar(featureJar.toString()); + options.setSplitNonClassResources(true); + DexSplitter.run(options); + Path baseDir = output.resolve("base"); + Path feature1Dir = output.resolve("feature1"); + Path feature2Dir = output.resolve("feature2"); + Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt"); + Path feature1TextFile = feature1Dir.resolve("dexsplitsample/TextFile2.txt"); + Path feature2TextFile = feature2Dir.resolve("dexsplitsample/TextFile2.txt"); + assert !Files.exists(feature1TextFile); + assert !Files.exists(feature2TextFile); + assert Files.exists(baseTextFile); + assert Arrays.equals(Files.readAllBytes(baseTextFile), fileBytes); + } +}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java index bd7929d..05d6d3d 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -17,9 +17,12 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.ArtCommandBuilder; import com.android.tools.r8.ToolHelper.ProcessResult; +import com.android.tools.r8.dexsplitter.DexSplitter.Options; import com.android.tools.r8.utils.ArchiveResourceProvider; import com.android.tools.r8.utils.Pair; +import com.android.tools.r8.utils.ThrowingConsumer; import com.android.tools.r8.utils.ZipUtils; +import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import dalvik.system.PathClassLoader; import java.io.IOException; @@ -28,7 +31,9 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.Set; +import java.util.function.Consumer; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; @@ -150,6 +155,96 @@ toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime()); } + // Compile the passed in classes plus RunInterface and SplitRunner using R8, then split + // based on the base/feature sets. toRun must implement the BaseRunInterface + <E extends Throwable> ProcessResult testDexSplitter( + TestParameters parameters, + Set<Class<?>> baseClasses, + Set<Class<?>> featureClasses, + Class<?> toRun, + String expectedOutput, + ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer, + Consumer<R8FullTestBuilder> r8TestConfigurator) + throws Exception, E { + List<Class<?>> baseClassesWithRunner = + ImmutableList.<Class<?>>builder() + .add(RunInterface.class, SplitRunner.class) + .addAll(baseClasses) + .build(); + + Path baseJar = jarTestClasses(baseClassesWithRunner); + Path featureJar = jarTestClasses(featureClasses); + + Path featureOnly = + testForR8(parameters.getBackend()) + .addProgramClasses(featureClasses) + .addClasspathClasses(baseClasses) + .addClasspathClasses(RunInterface.class) + .addKeepAllClassesRule() + .addInliningAnnotations() + .setMinApi(parameters.getApiLevel()) + .compile() + .writeToZip(); + if (parameters.isDexRuntime()) { + // With D8 this should just work. We compile all of the base classes, then run with the + // feature loaded at runtime. Since there is no inlining/class merging we don't + // have any issues. + testForD8() + .addProgramClasses(SplitRunner.class, RunInterface.class) + .addProgramClasses(baseClasses) + .setMinApi(parameters.getApiLevel()) + .compile() + .run( + parameters.getRuntime(), + SplitRunner.class, + toRun.getName(), + featureOnly.toAbsolutePath().toString()) + .assertSuccessWithOutput(expectedOutput); + } + + R8FullTestBuilder builder = testForR8(parameters.getBackend()); + if (parameters.isCfRuntime()) { + // Compiling to jar we need to support the same way of loading code at runtime as + // android supports. + builder + .addProgramClasses(PathClassLoader.class) + .addKeepClassAndMembersRules(PathClassLoader.class); + } + + R8FullTestBuilder r8FullTestBuilder = + builder + .setMinApi(parameters.getApiLevel()) + .addProgramClasses(SplitRunner.class, RunInterface.class) + .addProgramClasses(baseClasses) + .addProgramClasses(featureClasses) + .addKeepMainRule(SplitRunner.class) + .addKeepClassRules(toRun); + r8TestConfigurator.accept(r8FullTestBuilder); + R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile(); + compileResultConsumer.accept(r8TestCompileResult); + Path fullFiles = r8TestCompileResult.writeToZip(); + + // Ensure that we can run the program as a unit (i.e., without splitting) + r8TestCompileResult + .run(parameters.getRuntime(), SplitRunner.class, toRun.getName()) + .assertSuccessWithOutput(expectedOutput); + + Path splitterOutput = temp.newFolder().toPath(); + Path splitterBaseDexFile = splitterOutput.resolve("base").resolve("classes.dex"); + Path splitterFeatureDexFile = splitterOutput.resolve("feature").resolve("classes.dex"); + + Options options = new Options(); + options.setOutput(splitterOutput.toString()); + options.addBaseJar(baseJar.toString()); + options.addFeatureJar(featureJar.toString(), "feature"); + + options.addInputArchive(fullFiles.toString()); + DexSplitter.run(options); + + return runFeatureOnArt( + toRun, splitterBaseDexFile, splitterFeatureDexFile, parameters.getRuntime()); + } + ProcessResult runFeatureOnArt( Class toRun, Path splitterBaseDexFile, Path splitterFeatureDexFile, TestRuntime runtime) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java index 1427b24..9a0d0ce 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -142,6 +142,7 @@ new IRCode( options, null, + Position.none(), blocks, new NumberGenerator(), basicBlockNumberGenerator,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java index 7bbc000..ea86918 100644 --- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java +++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -100,6 +100,7 @@ new IRCode( options, null, + Position.none(), blocks, new NumberGenerator(), basicBlockNumberGenerator, @@ -188,6 +189,7 @@ new IRCode( options, null, + Position.none(), blocks, new NumberGenerator(), basicBlockNumberGenerator,
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClass.kt b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClass.kt new file mode 100644 index 0000000..0d3a7da --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClass.kt
@@ -0,0 +1,18 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.reflection + +import kotlin.reflect.full.primaryConstructor +import kotlin.time.Duration + +@JvmInline +value class Value(private val rawValue: Int) + +data class Data(val value: Value) + +fun main() { + // See b/230369515 for context. + println(Data::class.primaryConstructor?.call(Value(0))?.value) +}
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java new file mode 100644 index 0000000..3b9e678 --- /dev/null +++ b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java
@@ -0,0 +1,129 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.kotlin.reflection; + +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion; +import com.android.tools.r8.KotlinTestBase; +import com.android.tools.r8.KotlinTestParameters; +import com.android.tools.r8.R8FullTestBuilder; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase; +import com.android.tools.r8.shaking.ProguardKeepAttributes; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.FileUtils; +import java.nio.file.Paths; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// See b/230369515 for context. +@RunWith(Parameterized.class) +public class ReflectiveConstructionWithInlineClassTest extends KotlinTestBase { + + private final TestParameters parameters; + private static final String EXPECTED_OUTPUT = "Value(rawValue=0)"; + private static final String PKG = + ReflectiveConstructionWithInlineClassTest.class.getPackage().getName(); + private static final String KOTLIN_FILE = "ReflectiveConstructionWithInlineClass"; + private static final String MAIN_CLASS = PKG + "." + KOTLIN_FILE + "Kt"; + + private static final KotlinCompileMemoizer compiledJars = + getCompileMemoizer( + Paths.get( + ToolHelper.TESTS_DIR, + "java", + DescriptorUtils.getBinaryNameFromJavaType(PKG), + KOTLIN_FILE + FileUtils.KT_EXTENSION)); + + @Parameters(name = "{0}, {1}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withAllRuntimesAndApiLevels().build(), + getKotlinTestParameters() + // Internal classes are supported from Kotlin 1.5. + .withCompilersStartingFromIncluding(KotlinCompilerVersion.KOTLINC_1_5_0) + .withOldCompilersStartingFrom(KotlinCompilerVersion.KOTLINC_1_5_0) + .withAllTargetVersions() + .build()); + } + + public ReflectiveConstructionWithInlineClassTest( + TestParameters parameters, KotlinTestParameters kotlinParameters) { + super(kotlinParameters); + this.parameters = parameters; + } + + @Test + public void testCf() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForJvm() + .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion)) + .addProgramFiles(kotlinc.getKotlinStdlibJar()) + .addProgramFiles(kotlinc.getKotlinReflectJar()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST)) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutputLines(EXPECTED_OUTPUT); + } + + @Test + public void testD8() throws Exception { + assumeTrue(parameters.isDexRuntime()); + testForD8(parameters.getBackend()) + .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion)) + .addProgramFiles(kotlinc.getKotlinStdlibJar()) + .addProgramFiles(kotlinc.getKotlinReflectJar()) + .setMinApi(parameters.getApiLevel()) + .addOptionsModification( + options -> { + options.testing.enableD8ResourcesPassThrough = true; + options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer(); + }) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutputLines(EXPECTED_OUTPUT); + } + + private R8FullTestBuilder configureR8(R8FullTestBuilder builder) { + return builder + .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion)) + .addProgramFiles(kotlinc.getKotlinStdlibJar()) + .addProgramFiles(kotlinc.getKotlinReflectJar()) + .addProgramFiles(kotlinc.getKotlinAnnotationJar()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST)) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(MAIN_CLASS) + .addKeepClassAndMembersRules(PKG + ".Data") + .addKeepEnumsRule() + .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS) + .allowDiagnosticMessages() + .allowUnusedDontWarnKotlinReflectJvmInternal(); + } + + @Test + public void testR8KeepDataClass() throws Exception { + configureR8(testForR8(parameters.getBackend())) + .compile() + .assertNoErrorMessages() + .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertFailureWithErrorThatThrows(IllegalArgumentException.class); + } + + @Test + public void testR8KeepDataClassAndInlineClass() throws Exception { + configureR8(testForR8(parameters.getBackend())) + .addKeepRules("-keep class " + PKG + ".Value { *; }") + .compile() + .assertNoErrorMessages() + .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutputLines(EXPECTED_OUTPUT); + } +}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java index 75b7dda..4d546d1 100644 --- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java +++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -25,6 +25,8 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.dex.ApplicationWriter; import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.dexsplitter.DexSplitter; +import com.android.tools.r8.dexsplitter.DexSplitter.Options; import com.android.tools.r8.errors.CompilationError; import com.android.tools.r8.errors.DexFileOverflowDiagnostic; import com.android.tools.r8.errors.Unreachable; @@ -253,6 +255,49 @@ } @Test + public void everyThirdClassInMainWithDexSplitter() throws Throwable { + List<String> featureMappings = new ArrayList<>(); + List<String> inFeatureMapping = new ArrayList<>(); + + ImmutableList.Builder<String> mainDexBuilder = ImmutableList.builder(); + for (int i = 0; i < MANY_CLASSES.size(); i++) { + String clazz = MANY_CLASSES.get(i); + // Write the first 2 classes into the split. + if (i < 10) { + featureMappings.add(clazz + ":feature1"); + inFeatureMapping.add(clazz); + } + if (i % 3 == 0) { + mainDexBuilder.add(clazz); + } + } + Path featureSplitMapping = temp.getRoot().toPath().resolve("splitmapping"); + Path mainDexFile = temp.getRoot().toPath().resolve("maindex"); + FileUtils.writeTextFile(featureSplitMapping, featureMappings); + List<String> mainDexList = mainDexBuilder.build(); + FileUtils.writeTextFile(mainDexFile, ListUtils.map(mainDexList, MainDexListTests::typeToEntry)); + Path output = temp.getRoot().toPath().resolve("split_output"); + Files.createDirectories(output); + TestDiagnosticsHandler diagnosticsHandler = new TestDiagnosticsHandler(); + Options options = new Options(diagnosticsHandler); + options.addInputArchive(getManyClassesMultiDexAppPath().toString()); + options.setFeatureSplitMapping(featureSplitMapping.toString()); + options.setOutput(output.toString()); + options.setMainDexList(mainDexFile.toString()); + DexSplitter.run(options); + assertEquals(0, diagnosticsHandler.numberOfErrorsAndWarnings()); + Path baseDir = output.resolve("base"); + CodeInspector inspector = + new CodeInspector( + AndroidApp.builder().addProgramFiles(baseDir.resolve("classes.dex")).build()); + for (String clazz : mainDexList) { + if (!inspector.clazz(clazz).isPresent() && !inFeatureMapping.contains(clazz)) { + failedToFindClassInExpectedFile(baseDir, clazz); + } + } + } + + @Test public void singleClassInMainDex() throws Throwable { ImmutableList<String> mainDex = ImmutableList.of(MANY_CLASSES.get(0)); verifyMainDexContains(mainDex, getManyClassesSingleDexAppPath(), true); @@ -969,7 +1014,7 @@ @Override public Position getCanonicalDebugPositionAtOffset(int offset) { - throw new Unreachable(); + return Position.none(); } @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/SoftPinDefaultInitializerWithConstClassInCompatModeTest.java b/src/test/java/com/android/tools/r8/shaking/SoftPinDefaultInitializerWithConstClassInCompatModeTest.java new file mode 100644 index 0000000..d553900 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/SoftPinDefaultInitializerWithConstClassInCompatModeTest.java
@@ -0,0 +1,52 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.shaking; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class SoftPinDefaultInitializerWithConstClassInCompatModeTest extends TestBase { + + @Parameter(0) + public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void test() throws Exception { + testForR8Compat(parameters.getBackend()) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .setMinApi(parameters.getApiLevel()) + .compile() + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("A"); + } + + static class Main { + public static void main(String[] args) throws Exception { + Class<?> clazz = System.currentTimeMillis() > 0 ? A.class : Object.class; + System.out.println(clazz.newInstance().toString()); + } + } + + static class A { + + @Override + public String toString() { + return "A"; + } + } +}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java index 889f7a7..9b4d076 100644 --- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java +++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1204,23 +1204,22 @@ } public ClassFileTransformer setMaxStackHeight(MethodPredicate predicate, int newMaxStack) { - return addMethodTransformer( - new MethodTransformer() { - @Override - public void visitMaxs(int maxStack, int maxLocals) { - super.visitMaxs( - MethodPredicate.testContext(predicate, getContext()) ? newMaxStack : maxStack, - maxLocals); - } - }); + return setMaxs(predicate, newMaxStack, null); } - public ClassFileTransformer computeMaxs() { + public ClassFileTransformer setMaxs( + MethodPredicate predicate, Integer newMaxStack, Integer newMaxLocals) { return addMethodTransformer( new MethodTransformer() { @Override public void visitMaxs(int maxStack, int maxLocals) { - super.visitMaxs(maxStack, maxLocals); + if (MethodPredicate.testContext(predicate, getContext())) { + super.visitMaxs( + newMaxStack != null ? newMaxStack : maxStack, + newMaxLocals != null ? newMaxLocals : maxLocals); + } else { + super.visitMaxs(maxStack, maxLocals); + } } }); }
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java new file mode 100644 index 0000000..f41be5c --- /dev/null +++ b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
@@ -0,0 +1,132 @@ +// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.utils; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; + +import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; + +public class FeatureClassMappingTest { + + @Test + public void testSimpleParse() throws Exception { + + List<String> lines = + ImmutableList.of( + "# Comment, don't care about contents: even more ::::", + "com.google.base:base", + "", // Empty lines allowed + "com.google.feature1:feature1", + "com.google.feature1:feature1", // Multiple definitions of the same predicate allowed. + "com.google$:feature1", + "_com.google:feature21", + "com.google.*:feature32"); + FeatureClassMapping mapping = new FeatureClassMapping(lines); + } + + private void ensureThrowsMappingException(List<String> lines) { + try { + new FeatureClassMapping(lines); + assertFalse(true); + } catch (FeatureMappingException e) { + // Expected + } + } + + private void ensureThrowsMappingException(String string) { + ensureThrowsMappingException(ImmutableList.of(string)); + } + + @Test + public void testLookup() throws Exception { + List<String> lines = + ImmutableList.of( + "com.google.Base:base", + "", + "com.google.Feature1:feature1", + "com.google.Feature1:feature1", // Multiple definitions of the same predicate allowed. + "com.google.different.*:feature1", + "_com.Google:feature21", + "com.google.bas42.*:feature42"); + FeatureClassMapping mapping = new FeatureClassMapping(lines); + assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1"); + assertEquals(mapping.featureForClass("com.google.different.Feature1"), "feature1"); + assertEquals(mapping.featureForClass("com.google.different.Foobar"), "feature1"); + assertEquals(mapping.featureForClass("com.google.Base"), "base"); + assertEquals(mapping.featureForClass("com.google.bas42.foo.bar.bar.Foo"), "feature42"); + assertEquals(mapping.featureForClass("com.google.bas42.f$o$o$.bar43.bar.Foo"), "feature42"); + assertEquals(mapping.featureForClass("_com.Google"), "feature21"); + } + + @Test + public void testCatchAllWildcards() throws Exception { + testBaseWildcard(true); + testBaseWildcard(false); + testNonBaseCatchAll(); + } + + private void testNonBaseCatchAll() throws FeatureMappingException { + List<String> lines = + ImmutableList.of( + "com.google.Feature1:feature1", + "*:nonbase", + "com.strange.*:feature2"); + FeatureClassMapping mapping = new FeatureClassMapping(lines); + assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1"); + assertEquals(mapping.featureForClass("com.google.different.Feature1"), "nonbase"); + assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2"); + assertEquals(mapping.featureForClass("Feature1"), "nonbase"); + assertEquals(mapping.featureForClass("a.b.z.A"), "nonbase"); + } + + private void testBaseWildcard(boolean explicitBase) throws FeatureMappingException { + List<String> lines = + ImmutableList.of( + "com.google.Feature1:feature1", + explicitBase ? "*:base" : "", + "com.strange.*:feature2"); + FeatureClassMapping mapping = new FeatureClassMapping(lines); + assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1"); + assertEquals(mapping.featureForClass("com.google.different.Feature1"), "base"); + assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2"); + assertEquals(mapping.featureForClass("com.stranger.Clazz"), "base"); + assertEquals(mapping.featureForClass("Feature1"), "base"); + assertEquals(mapping.featureForClass("a.b.z.A"), "base"); + } + + @Test + public void testWrongLines() throws Exception { + // No colon. + ensureThrowsMappingException("foo"); + ensureThrowsMappingException("com.google.base"); + // Two colons. + ensureThrowsMappingException(ImmutableList.of("a:b:c")); + + // Empty identifier. + ensureThrowsMappingException("com..google:feature1"); + + // Ambiguous redefinition + ensureThrowsMappingException( + ImmutableList.of("com.google.foo:feature1", "com.google.foo:feature2")); + ensureThrowsMappingException( + ImmutableList.of("com.google.foo.*:feature1", "com.google.foo.*:feature2")); + } + + @Test + public void testUsesOnlyExactMappings() throws Exception { + List<String> lines = + ImmutableList.of( + "com.pkg1.Clazz:feature1", + "com.pkg2.Clazz:feature2"); + FeatureClassMapping mapping = new FeatureClassMapping(lines); + + assertEquals(mapping.featureForClass("com.pkg1.Clazz"), "feature1"); + assertEquals(mapping.featureForClass("com.pkg2.Clazz"), "feature2"); + assertEquals(mapping.featureForClass("com.pkg1.Other"), mapping.baseName); + } +}
diff --git a/tools/desugar_jdk_libs_repository.py b/tools/desugar_jdk_libs_repository.py new file mode 100755 index 0000000..4cace6a --- /dev/null +++ b/tools/desugar_jdk_libs_repository.py
@@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +import argparse +import os +from os.path import join +import shutil +import subprocess +import sys + +import utils +import create_maven_release + +def parse_options(): + result = argparse.ArgumentParser( + description='Local desugared library repository for JDK 11 legacy configuration') + result.add_argument('--repo-root', '--repo_root', + default='/tmp/repo', + metavar=('<path>'), + help='Location for Maven repository.') + args = result.parse_args() + return args + +def jar_or_pom_file(unzip_dir, artifact, version, extension): + return join( + unzip_dir, + 'com', + 'android', + 'tools', + artifact, + version, + artifact + '-' + version + '.' + extension) + +def jar_file(unzip_dir, artifact, version): + return jar_or_pom_file(unzip_dir, artifact, version, 'jar') + +def pom_file(unzip_dir, artifact, version): + return jar_or_pom_file(unzip_dir, artifact, version, 'pom') + +def main(): + args = parse_options() + shutil.rmtree(args.repo_root, ignore_errors=True) + utils.makedirs_if_needed(args.repo_root) + with utils.TempDir() as tmp_dir: + version = utils.desugar_configuration_version(utils.DESUGAR_CONFIGURATION_JDK11_LEGACY) + + # Checkout desugar_jdk_libs from GitHub + checkout_dir = join(tmp_dir, 'desugar_jdk_libs') + utils.RunCmd(['git', 'clone', 'https://github.com/google/desugar_jdk_libs.git', checkout_dir]) + with utils.ChangedWorkingDirectory(checkout_dir): + with open('VERSION_JDK11.txt') as version_file: + version_file_lines = version_file.readlines() + for line in version_file_lines: + if not line.startswith('#'): + desugar_jdk_libs_version = line.strip() + if (version != desugar_jdk_libs_version): + raise Exception( + "Version mismatch. Configuration has version '" + + version + + "', and desugar_jdk_libs has version '" + + desugar_jdk_libs_version + + "'") + + # Build desugared library configuration. + print("Building desugared library configuration " + version) + maven_zip = join(tmp_dir, 'desugar_configuration.zip') + create_maven_release.generate_desugar_configuration_maven_zip( + maven_zip, + utils.DESUGAR_CONFIGURATION_JDK11_LEGACY, + utils.DESUGAR_IMPLEMENTATION_JDK11) + unzip_dir = join(tmp_dir, 'desugar_jdk_libs_configuration_unzipped') + cmd = ['unzip', '-q', maven_zip, '-d', unzip_dir] + utils.RunCmd(cmd) + cmd = [ + 'mvn', + 'deploy:deploy-file', + '-Durl=file:' + args.repo_root, + '-DrepositoryId=someName', + '-Dfile=' + jar_file(unzip_dir, 'desugar_jdk_libs_configuration', version), + '-DpomFile=' + pom_file(unzip_dir, 'desugar_jdk_libs_configuration', version)] + utils.RunCmd(cmd) + + # Build desugared library. + print("Building desugared library " + version) + with utils.ChangedWorkingDirectory(checkout_dir): + utils.RunCmd([ + 'bazel', + '--bazelrc=/dev/null', + 'build', + '--spawn_strategy=local', + '--verbose_failures', + ':maven_release_jdk11']) + unzip_dir = join(tmp_dir, 'desugar_jdk_libs_unzipped') + cmd = [ + 'unzip', + '-q', + join(checkout_dir, 'bazel-bin', 'desugar_jdk_libs_jdk11.zip'), + '-d', + unzip_dir] + utils.RunCmd(cmd) + cmd = [ + 'mvn', + 'deploy:deploy-file', + '-Durl=file:' + args.repo_root, + '-DrepositoryId=someName', + '-Dfile=' + jar_file(unzip_dir, 'desugar_jdk_libs', version), + '-DpomFile=' + pom_file(unzip_dir, 'desugar_jdk_libs', version)] + utils.RunCmd(cmd) + + print() + print("Artifacts:") + print(" com.android.tools:desugar_jdk_libs_configuration:" + version) + print(" com.android.tools:desugar_jdk_libs:" + version) + print() + print("deployed to Maven repository at " + args.repo_root + ".") + print() + print("Add") + print() + print(" maven {") + print(" url uri('file://" + args.repo_root + "')") + print(" }") + print() + print("to dependencyResolutionManagement.repositories in settings.gradle.") + print() + print("Remember to run gradle with --refresh-dependencies " + + "(./gradlew --refresh-dependencies ...) " + + "to ensure the cache is not used when the same version is published.") + +if __name__ == '__main__': + sys.exit(main())
diff --git a/tools/dexsplitter.py b/tools/dexsplitter.py new file mode 100755 index 0000000..a97d089 --- /dev/null +++ b/tools/dexsplitter.py
@@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +import sys +import toolhelper + +if __name__ == '__main__': + sys.exit(toolhelper.run('dexsplitter', sys.argv[1:]))
diff --git a/tools/internal_test.py b/tools/internal_test.py index 45dd694..a726e1e 100755 --- a/tools/internal_test.py +++ b/tools/internal_test.py
@@ -287,9 +287,6 @@ print('Tests failed, you can print the logs by running(googlers only):') print(' tools/internal_test.py --print_logs %s' % git_hash) return 1 - else: - print(' Test validation of archiving logs, see b/177799191') - print(' tools/internal_test.py --print_logs %s' % git_hash) def run_continuously(): # If this script changes, we will restart ourselves @@ -377,7 +374,7 @@ stderr_fd.close() if stdout_fd: stdout_fd.close() - if exitcode != 0 or True: + if exitcode != 0: handle_output(archive, stderr, stdout, popen.returncode, timed_out, ' '.join(cmd)) return exitcode
diff --git a/tools/test.py b/tools/test.py index 55dbac0..27c07de 100755 --- a/tools/test.py +++ b/tools/test.py
@@ -225,8 +225,7 @@ relative_file = absolute_file[len(upload_dir)+1:] if (should_upload(relative_file, absolute_file)): utils.upload_file_to_cloud_storage(absolute_file, - destination_dir + relative_file, - public_read=False) + destination_dir + relative_file) url = 'https://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, file_name) print('Test results available at: %s' % url)
diff --git a/tools/utils.py b/tools/utils.py index 6bab90b..27a8feb 100644 --- a/tools/utils.py +++ b/tools/utils.py
@@ -105,7 +105,7 @@ def archive_file(name, gs_dir, src_file): gs_file = '%s/%s' % (gs_dir, name) - upload_file_to_cloud_storage(src_file, gs_file, public_read=False) + upload_file_to_cloud_storage(src_file, gs_file) def archive_value(name, gs_dir, value): with TempDir() as temp: @@ -338,7 +338,7 @@ def get_gsutil(): return 'gsutil.py' if os.name != 'nt' else 'gsutil.py.bat' -def upload_dir_to_cloud_storage(directory, destination, is_html=False, public_read=True): +def upload_dir_to_cloud_storage(directory, destination, is_html=False): # Upload and make the content encoding right for viewing directly cmd = [get_gsutil(), '-m', 'cp'] if is_html: @@ -347,7 +347,7 @@ PrintCmd(cmd) subprocess.check_call(cmd) -def upload_file_to_cloud_storage(source, destination, public_read=True): +def upload_file_to_cloud_storage(source, destination): cmd = [get_gsutil(), 'cp'] cmd += [source, destination] PrintCmd(cmd)