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)