Merge commit 'f33d3ca958fdb936e8053353216dde31b682d202' into dev-release
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
index 089a293..b02dc00 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
@@ -28,24 +28,23 @@
"j$/nio/file/spi/FileTypeDetector",
"j$/nio/file/Path",
"j$/nio/file/WatchEvent",
- "j$/nio/file/OpenOption");
+ "j$/nio/file/OpenOption",
+ "j$/nio/file/attribute/FileAttribute");
- static Map<String, String> getJavaWrapConvertOwnerMap() {
- return computeConvertOwnerMap("$Wrapper");
- }
-
- static Map<String, String> getJ$WrapConvertOwnerMap() {
- return computeConvertOwnerMap("$VivifiedWrapper");
- }
-
- private static HashMap<String, String> computeConvertOwnerMap(String suffix) {
+ static Map<String, String> getWrapConvertOwnerMap() {
HashMap<String, String> map = new HashMap<>();
for (String theEnum : ENUM_WRAP_CONVERT_OWNER) {
map.put(theEnum, theEnum + "$EnumConversion");
+ map.put(withJavaPrefix(theEnum), theEnum + "$EnumConversion");
}
for (String owner : WRAP_CONVERT_OWNER) {
- map.put(owner, owner + suffix);
+ map.put(withJavaPrefix(owner), owner + "$Wrapper");
+ map.put(owner, owner + "$VivifiedWrapper");
}
return map;
}
+
+ private static String withJavaPrefix(String descriptor) {
+ return "java" + descriptor.substring(2);
+ }
}
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
index 0848c09..95a5ecc 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriter.java
@@ -39,10 +39,8 @@
}
private final CustomConversionVersion legacy;
- private final Map<String, String> javaWrapConvertOwnerMap =
- CustomConversionAsmRewriteDescription.getJavaWrapConvertOwnerMap();
- private final Map<String, String> j$WrapConvertOwnerMap =
- CustomConversionAsmRewriteDescription.getJ$WrapConvertOwnerMap();
+ private final Map<String, String> wrapConvertOwnerMap =
+ CustomConversionAsmRewriteDescription.getWrapConvertOwnerMap();
public static void generateJars(Path jar, Path outputDirectory) throws IOException {
for (CustomConversionVersion version : CustomConversionVersion.values()) {
@@ -148,30 +146,14 @@
private void convertInvoke(int opcode, String owner, String descriptor, boolean isInterface) {
String firstArg = extractFirstArg(descriptor);
- assert sameBaseName(firstArg, owner);
- if (!javaWrapConvertOwnerMap.containsKey(owner)
- || !j$WrapConvertOwnerMap.containsKey(owner)
+ if (!wrapConvertOwnerMap.containsKey(firstArg)
|| !(firstArg.startsWith("java") || firstArg.startsWith("j$"))) {
throw new RuntimeException(
"Cannot transform wrap_convert method for " + firstArg + " (owner: " + owner + ")");
}
- if (firstArg.startsWith("java")) {
- String newOwner = javaWrapConvertOwnerMap.get(owner);
- super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface);
- return;
- }
- assert firstArg.startsWith("j$");
- String newOwner = j$WrapConvertOwnerMap.get(owner);
+ String newOwner = wrapConvertOwnerMap.get(firstArg);
super.visitMethodInsn(opcode, newOwner, CONVERT, descriptor, isInterface);
}
- private boolean sameBaseName(String firstArg, String owner) {
- assert owner.startsWith("j$");
- if (firstArg.equals(owner)) {
- return true;
- }
- String javaName = owner.replace("j$", "java");
- return firstArg.equals(javaName);
- }
}
}
diff --git a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
index 027d74f..42a3f91 100644
--- a/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
+++ b/src/library_desugar/java/desugar/sun/nio/fs/DesugarAndroidFileSystemProvider.java
@@ -9,8 +9,13 @@
import java.nio.channels.DesugarChannels;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
+import java.nio.file.CopyOption;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.spi.FileSystemProvider;
import java.util.Set;
@@ -28,6 +33,45 @@
}
@Override
+ public void copy(Path source, Path target, CopyOption... options) throws IOException {
+ if (!containsCopyOption(options, StandardCopyOption.REPLACE_EXISTING) && Files.exists(target)) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+ if (containsCopyOption(options, StandardCopyOption.ATOMIC_MOVE)) {
+ throw new UnsupportedOperationException("Unsupported copy option");
+ }
+ super.copy(source, target, options);
+ }
+
+ @Override
+ public void move(Path source, Path target, CopyOption... options) throws IOException {
+ if (!containsCopyOption(options, StandardCopyOption.REPLACE_EXISTING) && Files.exists(target)) {
+ throw new FileAlreadyExistsException(target.toString());
+ }
+ if (containsCopyOption(options, StandardCopyOption.COPY_ATTRIBUTES)) {
+ throw new UnsupportedOperationException("Unsupported copy option");
+ }
+ super.move(source, target, options);
+ }
+
+ private boolean containsCopyOption(CopyOption[] options, CopyOption option) {
+ for (CopyOption copyOption : options) {
+ if (copyOption == option) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+ if (dir.getParent() != null && !Files.exists(dir.getParent())) {
+ throw new NoSuchFileException(dir.toString());
+ }
+ super.createDirectory(dir, attrs);
+ }
+
+ @Override
public SeekableByteChannel newByteChannel(
Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException {
if (path.toFile().isDirectory()) {
diff --git a/src/library_desugar/java/j$/nio/file/attribute/AclFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/AclFileAttributeView.java
new file mode 100644
index 0000000..1b9da3d
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/AclFileAttributeView.java
@@ -0,0 +1,7 @@
+// 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 j$.nio.file.attribute;
+
+public class AclFileAttributeView {}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java
index 774cf2c..7d2612f 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributeView.java
@@ -5,13 +5,4 @@
package j$.nio.file.attribute;
public class BasicFileAttributeView extends FileAttributeView {
- public static java.nio.file.attribute.BasicFileAttributeView wrap_convert(
- j$.nio.file.attribute.BasicFileAttributeView fileAttributeView) {
- return null;
- }
-
- public static j$.nio.file.attribute.BasicFileAttributeView wrap_convert(
- java.nio.file.attribute.BasicFileAttributeView fileAttributeView) {
- return null;
- }
}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java
index dd91a95..69b0ca6 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/BasicFileAttributes.java
@@ -5,13 +5,4 @@
package j$.nio.file.attribute;
public class BasicFileAttributes {
- public static java.nio.file.attribute.BasicFileAttributes wrap_convert(
- j$.nio.file.attribute.BasicFileAttributes fileAttributes) {
- return null;
- }
-
- public static j$.nio.file.attribute.BasicFileAttributes wrap_convert(
- java.nio.file.attribute.BasicFileAttributes fileAttributes) {
- return null;
- }
}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributeView.java
new file mode 100644
index 0000000..899c1e0
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributeView.java
@@ -0,0 +1,7 @@
+// 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 j$.nio.file.attribute;
+
+public class DosFileAttributeView extends FileAttributeView {}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributes.java b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributes.java
new file mode 100644
index 0000000..ae2200c
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/DosFileAttributes.java
@@ -0,0 +1,7 @@
+// 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 j$.nio.file.attribute;
+
+public class DosFileAttributes {}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileAttribute.java b/src/library_desugar/java/j$/nio/file/attribute/FileAttribute.java
new file mode 100644
index 0000000..b971b05
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileAttribute.java
@@ -0,0 +1,12 @@
+// 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 j$.nio.file.attribute;
+
+public interface FileAttribute<T> {
+
+ String name();
+
+ T value();
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java
index 34046eb3..df33474 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeView.java
@@ -5,13 +5,4 @@
package j$.nio.file.attribute;
public class FileAttributeView {
- public static java.nio.file.attribute.FileAttributeView wrap_convert(
- j$.nio.file.attribute.FileAttributeView fileAttributeView) {
- return null;
- }
-
- public static j$.nio.file.attribute.FileAttributeView wrap_convert(
- java.nio.file.attribute.FileAttributeView fileAttributeView) {
- return null;
- }
}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileAttributeWrapperMethods.java b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeWrapperMethods.java
new file mode 100644
index 0000000..dfff944
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileAttributeWrapperMethods.java
@@ -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 j$.nio.file.attribute;
+
+public class FileAttributeWrapperMethods {
+
+ public static java.nio.file.attribute.FileAttribute wrap_convert(
+ j$.nio.file.attribute.FileAttribute fileAttribute) {
+ return null;
+ }
+
+ public static j$.nio.file.attribute.FileAttribute wrap_convert(
+ java.nio.file.attribute.FileAttribute fileAttribute) {
+ return null;
+ }
+}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java
index 34a637d..cbce1b4 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/FileOwnerAttributeView.java
@@ -5,13 +5,4 @@
package j$.nio.file.attribute;
public class FileOwnerAttributeView extends FileAttributeView {
- public static java.nio.file.attribute.FileOwnerAttributeView wrap_convert(
- j$.nio.file.attribute.FileOwnerAttributeView fileAttributeView) {
- return null;
- }
-
- public static j$.nio.file.attribute.FileOwnerAttributeView wrap_convert(
- java.nio.file.attribute.FileOwnerAttributeView fileAttributeView) {
- return null;
- }
}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java
index bb24cb7..04f4f04 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributeView.java
@@ -5,13 +5,4 @@
package j$.nio.file.attribute;
public class PosixFileAttributeView extends FileAttributeView {
- public static java.nio.file.attribute.PosixFileAttributeView wrap_convert(
- j$.nio.file.attribute.PosixFileAttributeView fileAttributeView) {
- return null;
- }
-
- public static j$.nio.file.attribute.PosixFileAttributeView wrap_convert(
- java.nio.file.attribute.PosixFileAttributeView fileAttributeView) {
- return null;
- }
}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java
index 5e61814..d6e9796 100644
--- a/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java
+++ b/src/library_desugar/java/j$/nio/file/attribute/PosixFileAttributes.java
@@ -5,13 +5,4 @@
package j$.nio.file.attribute;
public class PosixFileAttributes extends BasicFileAttributes {
- public static java.nio.file.attribute.PosixFileAttributes wrap_convert(
- j$.nio.file.attribute.PosixFileAttributes fileAttributes) {
- return null;
- }
-
- public static j$.nio.file.attribute.PosixFileAttributes wrap_convert(
- java.nio.file.attribute.PosixFileAttributes fileAttributes) {
- return null;
- }
}
diff --git a/src/library_desugar/java/j$/nio/file/attribute/UserDefinedFileAttributeView.java b/src/library_desugar/java/j$/nio/file/attribute/UserDefinedFileAttributeView.java
new file mode 100644
index 0000000..490fcec
--- /dev/null
+++ b/src/library_desugar/java/j$/nio/file/attribute/UserDefinedFileAttributeView.java
@@ -0,0 +1,7 @@
+// 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 j$.nio.file.attribute;
+
+public class UserDefinedFileAttributeView {}
diff --git a/src/library_desugar/java/java/nio/file/FileApiFlips.java b/src/library_desugar/java/java/nio/file/FileApiFlips.java
index 9c8a46a..3ad0a61 100644
--- a/src/library_desugar/java/java/nio/file/FileApiFlips.java
+++ b/src/library_desugar/java/java/nio/file/FileApiFlips.java
@@ -23,15 +23,21 @@
if (attributesClass == j$.nio.file.attribute.BasicFileAttributes.class) {
return java.nio.file.attribute.BasicFileAttributes.class;
}
- if (attributesClass == j$.nio.file.attribute.PosixFileAttributes.class) {
- return java.nio.file.attribute.PosixFileAttributes.class;
- }
if (attributesClass == java.nio.file.attribute.BasicFileAttributes.class) {
return j$.nio.file.attribute.BasicFileAttributes.class;
}
+ if (attributesClass == j$.nio.file.attribute.PosixFileAttributes.class) {
+ return java.nio.file.attribute.PosixFileAttributes.class;
+ }
if (attributesClass == java.nio.file.attribute.PosixFileAttributes.class) {
return j$.nio.file.attribute.PosixFileAttributes.class;
}
+ if (attributesClass == java.nio.file.attribute.DosFileAttributes.class) {
+ return j$.nio.file.attribute.DosFileAttributes.class;
+ }
+ if (attributesClass == j$.nio.file.attribute.DosFileAttributes.class) {
+ return java.nio.file.attribute.DosFileAttributes.class;
+ }
throw exception("java.nio.file.attribute.BasicFileAttributes", attributesClass);
}
@@ -42,21 +48,41 @@
if (attributeView == j$.nio.file.attribute.BasicFileAttributeView.class) {
return java.nio.file.attribute.BasicFileAttributeView.class;
}
- if (attributeView == j$.nio.file.attribute.PosixFileAttributeView.class) {
- return java.nio.file.attribute.PosixFileAttributeView.class;
- }
- if (attributeView == j$.nio.file.attribute.FileOwnerAttributeView.class) {
- return java.nio.file.attribute.FileOwnerAttributeView.class;
- }
if (attributeView == java.nio.file.attribute.BasicFileAttributeView.class) {
return j$.nio.file.attribute.BasicFileAttributeView.class;
}
+ if (attributeView == j$.nio.file.attribute.PosixFileAttributeView.class) {
+ return java.nio.file.attribute.PosixFileAttributeView.class;
+ }
if (attributeView == java.nio.file.attribute.PosixFileAttributeView.class) {
return j$.nio.file.attribute.PosixFileAttributeView.class;
}
+ if (attributeView == j$.nio.file.attribute.FileOwnerAttributeView.class) {
+ return java.nio.file.attribute.FileOwnerAttributeView.class;
+ }
if (attributeView == java.nio.file.attribute.FileOwnerAttributeView.class) {
return j$.nio.file.attribute.FileOwnerAttributeView.class;
}
+ // The following ones don't seem to work on any version in Android, but it's still possible
+ // for generic java code to use them and get null as a result.
+ if (attributeView == java.nio.file.attribute.DosFileAttributeView.class) {
+ return j$.nio.file.attribute.DosFileAttributeView.class;
+ }
+ if (attributeView == j$.nio.file.attribute.DosFileAttributeView.class) {
+ return java.nio.file.attribute.DosFileAttributeView.class;
+ }
+ if (attributeView == java.nio.file.attribute.UserDefinedFileAttributeView.class) {
+ return j$.nio.file.attribute.UserDefinedFileAttributeView.class;
+ }
+ if (attributeView == j$.nio.file.attribute.UserDefinedFileAttributeView.class) {
+ return java.nio.file.attribute.UserDefinedFileAttributeView.class;
+ }
+ if (attributeView == java.nio.file.attribute.AclFileAttributeView.class) {
+ return j$.nio.file.attribute.AclFileAttributeView.class;
+ }
+ if (attributeView == j$.nio.file.attribute.AclFileAttributeView.class) {
+ return java.nio.file.attribute.AclFileAttributeView.class;
+ }
throw exception("java.nio.file.attribute.FileAttributeView", attributeView);
}
diff --git a/src/library_desugar/java/java/nio/file/PathApiFlips.java b/src/library_desugar/java/java/nio/file/PathApiFlips.java
index 7314b7f..8747fae 100644
--- a/src/library_desugar/java/java/nio/file/PathApiFlips.java
+++ b/src/library_desugar/java/java/nio/file/PathApiFlips.java
@@ -104,11 +104,6 @@
public void remove() {
iterator.remove();
}
-
- @Override
- public void forEachRemaining(Consumer<? super T> action) {
- iterator.forEachRemaining(path -> action.accept(convertPath(path)));
- }
}
public static class DirectoryStreamPathWrapper<T> implements DirectoryStream<T> {
diff --git a/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java b/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
index c895d52..18c575f 100644
--- a/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
+++ b/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
@@ -4,6 +4,10 @@
package java.nio.file.attribute;
+import java.nio.file.FileApiFlips;
+import java.util.Collections;
+import java.util.Set;
+
public class FileAttributeConversions {
public static java.nio.file.attribute.FileTime convert(j$.nio.file.attribute.FileTime fileTime) {
@@ -20,4 +24,58 @@
return j$.nio.file.attribute.FileTime.fromMillis(fileTime.toMillis());
}
+ /** A FileAttribute usually holds an immutable set of non-null posix permissions. */
+ public static java.nio.file.attribute.FileAttribute<?> convert(
+ j$.nio.file.attribute.FileAttribute<?> attribute) {
+ if (attribute == null) {
+ return null;
+ }
+ if (isPosixPermissionAttributes(attribute.value())) {
+ return new java.nio.file.attribute.FileAttribute<Object>() {
+ public String name() {
+ return "posix:permissions";
+ }
+
+ public Object value() {
+ return Collections.unmodifiableSet(
+ FileApiFlips.flipPosixPermissionSet((Set<?>) attribute.value()));
+ }
+ };
+ }
+ return j$.nio.file.attribute.FileAttributeWrapperMethods.wrap_convert(attribute);
+ }
+
+ public static j$.nio.file.attribute.FileAttribute<?> convert(
+ java.nio.file.attribute.FileAttribute<?> attribute) {
+ if (attribute == null) {
+ return null;
+ }
+ if (isPosixPermissionAttributes(attribute.value())) {
+ return new j$.nio.file.attribute.FileAttribute<Object>() {
+ public String name() {
+ return "posix:permissions";
+ }
+
+ public Object value() {
+ return Collections.unmodifiableSet(
+ FileApiFlips.flipPosixPermissionSet((Set<?>) attribute.value()));
+ }
+ };
+ }
+ return j$.nio.file.attribute.FileAttributeWrapperMethods.wrap_convert(attribute);
+ }
+
+ private static boolean isPosixPermissionAttributes(Object value) {
+ if (value instanceof java.util.Set) {
+ Set<?> set = (java.util.Set<?>) value;
+ if (!set.isEmpty()) {
+ Object guineaPig = set.iterator().next();
+ if (guineaPig instanceof java.nio.file.attribute.PosixFilePermission
+ || guineaPig instanceof j$.nio.file.attribute.PosixFilePermission) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 244aa34..f4a3aef 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -233,6 +233,7 @@
"java.util.Map java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.String, java.nio.file.LinkOption[])" : [-1, "java.util.Map java.nio.file.FileApiFlips#flipMapWithMaybeFileTimeValues(java.util.Map)"],
"java.lang.Iterable java.nio.file.FileSystem#getRootDirectories()": [-1, "java.lang.Iterable java.nio.file.PathApiFlips#flipIterablePath(java.lang.Iterable)"],
"java.util.Iterator java.nio.file.Path#iterator()": [-1, "java.util.Iterator java.nio.file.PathApiFlips#flipIteratorPath(java.util.Iterator)"],
+ "int java.nio.file.Path#compareTo(java.lang.Object)": [0, "java.lang.Object java.nio.file.PathApiFlips#convertPath(java.lang.Object)"],
"java.nio.file.DirectoryStream java.nio.file.spi.FileSystemProvider#newDirectoryStream(java.nio.file.Path, java.nio.file.DirectoryStream$Filter)": [-1, "java.nio.file.DirectoryStream java.nio.file.PathApiFlips#flipDirectoryStreamPath(java.nio.file.DirectoryStream)", 1, "java.nio.file.DirectoryStream$Filter java.nio.file.PathApiFlips#flipDirectoryStreamFilterPath(java.nio.file.DirectoryStream$Filter)"],
"void java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])": [2, "java.lang.Object java.nio.file.FileApiFlips#flipMaybeFileTime(java.lang.Object)"],
"java.nio.channels.FileChannel java.nio.channels.FileChannel#open(java.nio.file.Path, java.util.Set, java.nio.file.attribute.FileAttribute[])" : [1, "java.util.Set java.nio.file.FileApiFlips#flipOpenOptionSet(java.util.Set)"]
@@ -257,17 +258,21 @@
"java.nio.file.CopyOption",
"java.nio.file.StandardCopyOption",
"java.nio.file.attribute.GroupPrincipal",
- "java.nio.file.attribute.FileAttribute",
"java.nio.file.attribute.UserPrincipal",
"java.nio.file.FileStore",
"java.nio.file.attribute.FileStoreAttributeView",
"java.nio.file.attribute.PosixFilePermission",
"java.nio.file.attribute.BasicFileAttributes",
+ "java.nio.file.attribute.DosFileAttributes",
"java.nio.file.attribute.PosixFileAttributes",
+ "java.nio.file.attribute.FileAttribute",
"java.nio.file.attribute.FileAttributeView",
"java.nio.file.attribute.FileOwnerAttributeView",
+ "java.nio.file.attribute.DosFileAttributeView",
"java.nio.file.attribute.PosixFileAttributeView",
- "java.nio.file.attribute.BasicFileAttributeView"
+ "java.nio.file.attribute.BasicFileAttributeView",
+ "java.nio.file.attribute.AclFileAttributeView",
+ "java.nio.file.attribute.UserDefinedFileAttributeView"
],
"wrapper_conversion_excluding": {
"java.nio.channels.AsynchronousFileChannel": [
@@ -277,7 +282,8 @@
]
},
"custom_conversion": {
- "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions"
+ "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions",
+ "java.nio.file.attribute.FileAttribute": "java.nio.file.attribute.FileAttributeConversions"
}
},
{
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index c6d8571..4337302 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -6,32 +6,28 @@
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
-import com.android.tools.r8.dex.code.CfOrDexInstruction;
import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
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.DexLibraryClass;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramDefinition;
-import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ThrowExceptionCode;
-import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.synthesis.CommittedItems;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.Arrays;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -45,108 +41,16 @@
*/
public class ApiReferenceStubber {
- private class ReferencesToApiLevelUseRegistry extends UseRegistry<ProgramMethod> {
-
- public ReferencesToApiLevelUseRegistry(ProgramMethod context) {
- super(appView, context);
- }
-
- @Override
- public void registerInitClass(DexType type) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInvokeVirtual(DexMethod method) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInvokeDirect(DexMethod method) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInvokeStatic(DexMethod method) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInvokeInterface(DexMethod method) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInvokeSuper(DexMethod method) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInstanceFieldRead(DexField field) {
- // Intentionally empty.
- }
-
- @Override
- public void registerInstanceFieldWrite(DexField field) {
- // Intentionally empty.
- }
-
- @Override
- public void registerStaticFieldRead(DexField field) {
- // Intentionally empty.
- }
-
- @Override
- public void registerStaticFieldWrite(DexField field) {
- // Intentionally empty.
- }
-
- @Override
- public void registerTypeReference(DexType type) {
- checkReferenceToLibraryClass(type);
- }
-
- @Override
- public void registerInstanceOf(DexType type) {
- checkReferenceToLibraryClass(type);
- }
-
- @Override
- public void registerConstClass(
- DexType type,
- ListIterator<? extends CfOrDexInstruction> iterator,
- boolean ignoreCompatRules) {
- checkReferenceToLibraryClass(type);
- }
-
- @Override
- public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
- checkReferenceToLibraryClass(type);
- }
-
- @Override
- public void registerExceptionGuard(DexType guard) {
- checkReferenceToLibraryClass(guard);
- }
-
- private void checkReferenceToLibraryClass(DexReference reference) {
- DexType rewrittenType = appView.graphLens().lookupType(reference.getContextType());
- findReferencedLibraryClasses(rewrittenType, getContext().getContextClass());
- }
- }
-
private final AppView<?> appView;
private final Map<DexLibraryClass, Set<ProgramDefinition>> referencingContexts =
new ConcurrentHashMap<>();
private final Set<DexLibraryClass> libraryClassesToMock = Sets.newConcurrentHashSet();
private final Set<DexType> seenTypes = Sets.newConcurrentHashSet();
private final AndroidApiLevelCompute apiLevelCompute;
- private final DexItemFactory factory;
public ApiReferenceStubber(AppView<?> appView) {
this.appView = appView;
apiLevelCompute = appView.apiLevelCompute();
- factory = appView.dexItemFactory();
}
public void run(ExecutorService executorService) throws ExecutionException {
@@ -195,7 +99,18 @@
.forEach(superType -> findReferencedLibraryClasses(superType, clazz));
clazz.forEachProgramMethodMatching(
DexEncodedMethod::hasCode,
- method -> method.registerCodeReferences(new ReferencesToApiLevelUseRegistry(method)));
+ method -> {
+ Code code = method.getDefinition().getCode();
+ if (!code.isDexCode()) {
+ return;
+ }
+ for (TryHandler handler : code.asDexCode().getHandlers()) {
+ for (TypeAddrPair pair : handler.pairs) {
+ DexType rewrittenType = appView.graphLens().lookupType(pair.getType());
+ findReferencedLibraryClasses(rewrittenType, clazz);
+ }
+ }
+ });
}
private void findReferencedLibraryClasses(DexType type, DexProgramClass context) {
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index 293e189..f00f611 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -100,7 +100,11 @@
public CfCodePrinter() {}
- public List<String> getImports() {
+ public Set<String> getImports() {
+ return imports;
+ }
+
+ public List<String> getImportsSorted() {
ArrayList<String> sorted = new ArrayList<>(imports);
sorted.sort(String::compareTo);
return sorted;
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 e8019d9..6ade9b2 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
@@ -47,6 +47,16 @@
this.ignoreCompatRules = ignoreCompatRules;
}
+ @Override
+ public boolean isConstClass() {
+ return true;
+ }
+
+ @Override
+ public CfConstClass asConstClass() {
+ return this;
+ }
+
public boolean ignoreCompatRules() {
return ignoreCompatRules;
}
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 5384772..09ec0f5 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
@@ -48,6 +48,11 @@
}
@Override
+ public CfInstanceFieldRead asInstanceFieldGet() {
+ return this;
+ }
+
+ @Override
public CfFieldInstruction createWithField(DexField otherField) {
return new CfInstanceFieldRead(otherField);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index f21fc0c..5fbed91 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -174,6 +174,14 @@
return false;
}
+ public CfNew asNew() {
+ return null;
+ }
+
+ public boolean isNew() {
+ return false;
+ }
+
public CfConstString asConstString() {
return null;
}
@@ -206,6 +214,10 @@
return false;
}
+ public CfInstanceFieldRead asInstanceFieldGet() {
+ return null;
+ }
+
public boolean isStaticFieldGet() {
return false;
}
@@ -420,4 +432,12 @@
public CfCheckCast asCheckCast() {
return null;
}
+
+ public boolean isConstClass() {
+ return false;
+ }
+
+ public CfConstClass asConstClass() {
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNew.java b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
index bbefd75..4ec3b43 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNew.java
@@ -56,6 +56,16 @@
}
@Override
+ public CfNew asNew() {
+ return this;
+ }
+
+ @Override
+ public boolean isNew() {
+ return true;
+ }
+
+ @Override
public CfTypeInstruction asTypeInstruction() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 369e60c..1536cd8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -191,9 +191,9 @@
* given type is *not* visited. The function indicates if traversal should continue or break. The
* result of the traversal is BREAK iff the function returned BREAK.
*/
- public TraversalContinuation<?, ?> traverseSuperTypes(
+ public <B> TraversalContinuation<B, ?> traverseSuperTypes(
final DexClass clazz,
- TriFunction<DexType, DexClass, Boolean, TraversalContinuation<?, ?>> fn) {
+ TriFunction<DexType, DexClass, Boolean, TraversalContinuation<B, ?>> fn) {
// We do an initial zero-allocation pass over the class super chain as it does not require a
// worklist/seen-set. Only if the traversal is not aborted and there actually are interfaces,
// do we continue traversal over the interface types. This is assuming that the second pass
@@ -206,7 +206,7 @@
if (currentClass.superType == null) {
break;
}
- TraversalContinuation<?, ?> stepResult =
+ TraversalContinuation<B, ?> stepResult =
fn.apply(currentClass.superType, currentClass, false);
if (stepResult.shouldBreak()) {
return stepResult;
@@ -226,7 +226,7 @@
while (currentClass != null) {
for (DexType iface : currentClass.interfaces.values) {
if (seen.add(iface)) {
- TraversalContinuation<?, ?> stepResult = fn.apply(iface, currentClass, true);
+ TraversalContinuation<B, ?> stepResult = fn.apply(iface, currentClass, true);
if (stepResult.shouldBreak()) {
return stepResult;
}
@@ -246,7 +246,7 @@
if (definition != null) {
for (DexType iface : definition.interfaces.values) {
if (seen.add(iface)) {
- TraversalContinuation<?, ?> stepResult = fn.apply(iface, definition, true);
+ TraversalContinuation<B, ?> stepResult = fn.apply(iface, definition, true);
if (stepResult.shouldBreak()) {
return stepResult;
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 535a891..5b71a42 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -400,18 +400,25 @@
private static class PrunePreambleMethodVisitor extends MethodVisitor {
+ private final AppView<?> appView;
private boolean inPreamble = true;
- public PrunePreambleMethodVisitor(MethodVisitor methodVisitor) {
+ public PrunePreambleMethodVisitor(MethodVisitor methodVisitor, AppView<?> appView) {
super(InternalOptions.ASM_VERSION, methodVisitor);
+ this.appView = appView;
}
@Override
public void visitLineNumber(int line, Label start) {
- if (!inPreamble || line != 0) {
- inPreamble = false;
- super.visitLineNumber(line, start);
+ if (line == 0) {
+ if (inPreamble) {
+ inPreamble = false;
+ return;
+ }
+ // We must be in R8 if inserting a zero-line entry outside the method preamble.
+ assert appView.enableWholeProgramOptimizations();
}
+ super.visitLineNumber(line, start);
}
}
@@ -440,7 +447,8 @@
|| (appView.enableWholeProgramOptimizations()
&& classFileVersion.isEqualTo(CfVersion.V1_6)
&& !options.shouldKeepStackMapTable());
- PrunePreambleMethodVisitor prunePreambleVisitor = new PrunePreambleMethodVisitor(visitor);
+ PrunePreambleMethodVisitor prunePreambleVisitor =
+ new PrunePreambleMethodVisitor(visitor, appView);
for (CfInstruction instruction : instructions) {
if (discardFrames && instruction instanceof CfFrame) {
continue;
@@ -911,7 +919,13 @@
oldPosition.getPosition().withOutermostCallerPosition(callerPosition)));
} else {
if (!instruction.isLabel() && !seenPosition) {
- newInstructions.add(new CfPosition(firstLabel, callerPosition));
+ Position preamblePosition =
+ SyntheticPosition.builder()
+ .setMethod(callee)
+ .setCallerPosition(callerPosition)
+ .setLine(0)
+ .build();
+ newInstructions.add(new CfPosition(firstLabel, preamblePosition));
seenPosition = true;
}
newInstructions.add(instruction);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index aeab5fe..3463789 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -41,6 +41,11 @@
}
@Override
+ public LookupTarget toLookupTarget(DexClassAndMethod classAndMethod) {
+ return classAndMethod;
+ }
+
+ @Override
public MethodAccessFlags getAccessFlags() {
return getDefinition().getAccessFlags();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 09db76d..048e01d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.dex.code.DexReturnVoid;
import com.android.tools.r8.dex.code.DexSwitchPayload;
import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
@@ -22,6 +23,8 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.PositionBuilder;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
import com.android.tools.r8.ir.code.Position.SyntheticPosition;
import com.android.tools.r8.ir.conversion.DexSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -30,6 +33,7 @@
import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.RetracerForCodePrinting;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.structural.Equatable;
@@ -290,27 +294,34 @@
DexMethod caller, DexMethod callee, DexItemFactory factory) {
Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
- Position inlinePosition =
- SyntheticPosition.builder()
- .setMethod(caller)
- .setCallerPosition(callerPosition)
- .disableLineCheck()
- .build();
if (eventBasedInfo == null) {
// If the method has no debug info we generate a preamble position to denote the inlining.
// This is consistent with the building IR for inlining which will always ensure the method
// has a position.
+ Position preamblePosition =
+ SyntheticPosition.builder()
+ .setMethod(callee)
+ .setCallerPosition(callerPosition)
+ .setLine(0)
+ .build();
return new EventBasedDebugInfo(
0,
new DexString[callee.getArity()],
- new DexDebugEvent[] {
- new SetPositionFrame(inlinePosition), factory.zeroChangeDefaultEvent
- });
+ new DexDebugEvent[] {new SetPositionFrame(preamblePosition)});
}
+ // The inline position should match the first actual callee position, so either its actual line
+ // at first instruction or it is a synthetic preamble.
+ int lineAtPcZero = findLineAtPcZero(callee, eventBasedInfo);
+ PositionBuilder<?, ?> frameBuilder =
+ lineAtPcZero == -1
+ ? SyntheticPosition.builder().setLine(0)
+ : SourcePosition.builder().setLine(lineAtPcZero);
DexDebugEvent[] oldEvents = eventBasedInfo.events;
DexDebugEvent[] newEvents = new DexDebugEvent[oldEvents.length + 1];
int i = 0;
- newEvents[i++] = new SetPositionFrame(inlinePosition);
+ newEvents[i++] =
+ new SetPositionFrame(
+ frameBuilder.setMethod(callee).setCallerPosition(callerPosition).build());
for (DexDebugEvent event : oldEvents) {
if (event instanceof SetPositionFrame) {
SetPositionFrame oldFrame = (SetPositionFrame) event;
@@ -325,6 +336,27 @@
return new EventBasedDebugInfo(eventBasedInfo.startLine, eventBasedInfo.parameters, newEvents);
}
+ private static int findLineAtPcZero(DexMethod method, EventBasedDebugInfo debugInfo) {
+ IntBox lineAtPcZero = new IntBox(-1);
+ DexDebugPositionState visitor =
+ new DexDebugPositionState(debugInfo.startLine, method) {
+ @Override
+ public void visit(Default defaultEvent) {
+ super.visit(defaultEvent);
+ if (getCurrentPc() == 0) {
+ lineAtPcZero.set(getCurrentLine());
+ }
+ }
+ };
+ for (DexDebugEvent event : debugInfo.events) {
+ event.accept(visitor);
+ if (visitor.getCurrentPc() > 0) {
+ break;
+ }
+ }
+ return lineAtPcZero.get();
+ }
+
public static int getLargestPrefix(DexItemFactory factory, DexString name) {
if (name != null && name.endsWith(factory.thisName)) {
String string = name.toString();
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
deleted file mode 100644
index 8d41561..0000000
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfoForSingleLineMethod.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2021, 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.graph;
-
-import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
-
-public class DexDebugInfoForSingleLineMethod extends EventBasedDebugInfo {
-
- private static final DexDebugInfoForSingleLineMethod INSTANCE =
- new DexDebugInfoForSingleLineMethod(0, DexString.EMPTY_ARRAY, DexDebugEvent.EMPTY_ARRAY);
-
- private DexDebugInfoForSingleLineMethod(
- int startLine, DexString[] parameters, DexDebugEvent[] events) {
- super(startLine, parameters, events);
- }
-
- public static DexDebugInfoForSingleLineMethod getInstance() {
- return INSTANCE;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 47fabfa..9807531 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -353,7 +353,7 @@
return new Builder(false);
}
- private static Builder builder(DexEncodedField from) {
+ public static Builder builder(DexEncodedField from) {
return new Builder(from.isD8R8Synthesized(), from);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 411e5ee..c1e856e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -75,6 +75,9 @@
public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
public static final String recordTagDescriptorString = "Lcom/android/tools/r8/RecordTag;";
public static final String recordDescriptorString = "Ljava/lang/Record;";
+ public static final String desugarVarHandleDescriptorString =
+ "Lcom/android/tools/r8/DesugarVarHandle;";
+ public static final String varHandleDescriptorString = "Ljava/lang/invoke/VarHandle;";
public static final String dalvikAnnotationOptimizationPrefixString =
"Ldalvik/annotation/optimization/";
@@ -266,7 +269,7 @@
public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
- public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
+ public final DexString varHandleDescriptor = createString(varHandleDescriptorString);
public final DexString methodHandleDescriptor = createString("Ljava/lang/invoke/MethodHandle;");
public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
public final DexString invocationHandlerDescriptor =
@@ -732,6 +735,8 @@
public final DexType stringConcatFactoryType =
createStaticallyKnownType("Ljava/lang/invoke/StringConcatFactory;");
public final DexType unsafeType = createStaticallyKnownType("Lsun/misc/Unsafe;");
+ public final DexType desugarVarHandleType =
+ createStaticallyKnownType(desugarVarHandleDescriptorString);
public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
diff --git a/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
index e1209e9..3ada6dc 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
@@ -29,6 +29,11 @@
}
@Override
+ public LookupTarget toLookupTarget(DexClassAndMethod classAndMethod) {
+ return new LookupLambdaTarget(lambda, classAndMethod);
+ }
+
+ @Override
public void accept(
Consumer<LookupMethodTarget> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer) {
lambdaConsumer.accept(this);
@@ -37,4 +42,9 @@
public DexClassAndMethod getImplementationMethod() {
return method;
}
+
+ @Override
+ public DexClassAndMethod getTargetOrImplementationMethod() {
+ return getImplementationMethod();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java b/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
index b7a6aa0..6df0bbd 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
@@ -30,4 +30,9 @@
DexEncodedMethod getDefinition();
DexClassAndMethod getTarget();
+
+ @Override
+ default DexClassAndMethod getTargetOrImplementationMethod() {
+ return getTarget();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java b/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
index d69c5b3..c21584e 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
@@ -15,6 +15,11 @@
}
@Override
+ public LookupTarget toLookupTarget(DexClassAndMethod classAndMethod) {
+ return new LookupMethodTargetWithAccessOverride(classAndMethod, accessOverride);
+ }
+
+ @Override
public DexClassAndMethod getAccessOverride() {
return accessOverride;
}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupTarget.java b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
index 4530c0f..a77ed1d 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
@@ -26,6 +26,10 @@
return null;
}
+ LookupTarget toLookupTarget(DexClassAndMethod classAndMethod);
+
void accept(
Consumer<LookupMethodTarget> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer);
+
+ DexClassAndMethod getTargetOrImplementationMethod();
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 94bb52d..8c949fe 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -348,9 +348,11 @@
if (type == null) {
return null;
}
- return appInfo
- .contextIndependentDefinitionForWithResolutionResult(type)
- .toSingleClassWithLibraryOverProgram();
+ ClassResolutionResult resolutionResult =
+ appInfo.contextIndependentDefinitionForWithResolutionResult(type);
+ return appInfo.options().lookupLibraryBeforeProgram
+ ? resolutionResult.toSingleClassWithLibraryOverProgram()
+ : resolutionResult.toSingleClassWithProgramOverLibrary();
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 30c3b67..2a543a8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
@@ -29,6 +30,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
@@ -37,6 +39,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
public class StaticFieldValueAnalysis extends FieldValueAnalysis {
@@ -210,7 +213,7 @@
if (value.isPhi()) {
return null;
}
- if (value.definition.isNewArrayEmpty()) {
+ if (value.definition.isNewArrayEmptyOrInvokeNewArray()) {
return computeSingleEnumFieldValueForValuesArray(value);
}
if (value.definition.isNewInstance()) {
@@ -220,7 +223,7 @@
}
private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
- if (!value.definition.isNewArrayEmpty()) {
+ if (!value.definition.isNewArrayEmptyOrInvokeNewArray()) {
return null;
}
AbstractValue valuesValue = computedValues.get(value);
@@ -241,26 +244,38 @@
}
private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
- assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
-
NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
- if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
+ InvokeNewArray invokeNewArray = value.definition.asInvokeNewArray();
+ assert newArrayEmpty != null || invokeNewArray != null;
+
+ DexType arrayType = newArrayEmpty != null ? newArrayEmpty.type : invokeNewArray.getArrayType();
+ if (arrayType.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
return null;
}
if (value.hasDebugUsers() || value.hasPhiUsers()) {
return null;
}
- if (!newArrayEmpty.size().isConstNumber()) {
- return null;
- }
- int valuesSize = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
- if (valuesSize == 0) {
- // No need to compute the state of an empty array.
+ int valuesSize = newArrayEmpty != null ? newArrayEmpty.sizeIfConst() : invokeNewArray.size();
+ if (valuesSize < 1) {
+ // Array is empty or non-const size.
return null;
}
ObjectState[] valuesState = new ObjectState[valuesSize];
+
+ if (invokeNewArray != null) {
+ // Populate array values from filled-new-array values.
+ List<Value> inValues = invokeNewArray.inValues();
+ for (int i = 0; i < valuesSize; ++i) {
+ if (!updateEnumValueState(valuesState, i, inValues.get(i))) {
+ return null;
+ }
+ }
+ }
+
+ // Populate / update array values from aput-object instructions, and find the static-put
+ // instruction.
DexEncodedField valuesField = null;
for (Instruction user : value.aliasedUsers()) {
switch (user.opcode()) {
@@ -276,18 +291,9 @@
if (index < 0 || index >= valuesSize) {
return null;
}
- ObjectState objectState = computeEnumInstanceObjectState(arrayPut.value());
- if (objectState == null || objectState.isEmpty()) {
- // We need the state of all fields for the analysis to be valuable.
+ if (!updateEnumValueState(valuesState, index, arrayPut.value())) {
return null;
}
- if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
- return null;
- }
- if (valuesState[index] != null) {
- return null;
- }
- valuesState[index] = objectState;
break;
case ASSUME:
@@ -328,24 +334,34 @@
.createSingleFieldValue(valuesField.getReference(), new EnumValuesObjectState(valuesState));
}
- private ObjectState computeEnumInstanceObjectState(Value value) {
+ private boolean updateEnumValueState(ObjectState[] valuesState, int index, Value value) {
Value root = value.getAliasedValue();
if (root.isPhi()) {
- return ObjectState.empty();
+ return false;
}
Instruction definition = root.getDefinition();
- if (definition.isNewInstance()) {
- return computeObjectState(definition.outValue());
- }
if (definition.isStaticGet()) {
// Enums with many instance rely on staticGets to set the $VALUES data instead of directly
// keeping the values in registers, due to the max capacity of the redundant field load
// elimination. The capacity has already been increased, so that this case is extremely
// uncommon (very large enums).
// TODO(b/169050248): We could consider analysing these to answer the object state here.
- return ObjectState.empty();
+ return false;
}
- return ObjectState.empty();
+ ObjectState objectState =
+ definition.isNewInstance() ? computeObjectState(definition.outValue()) : null;
+ if (objectState == null || objectState.isEmpty()) {
+ // We need the state of all fields for the analysis to be valuable.
+ return false;
+ }
+ if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+ return false;
+ }
+ if (valuesState[index] != null) {
+ return false;
+ }
+ valuesState[index] = objectState;
+ return true;
}
private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 74a3fb5..104b854 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -33,6 +33,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
@@ -46,6 +47,7 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.collect.Sets;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -316,17 +318,36 @@
Value sizeValue =
instructionIterator.insertConstIntInstruction(code, appView.options(), objects.size());
Value newObjectsValue = code.createValue(objectArrayType);
- instructionIterator.add(
- new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
// Populate the `objects` array.
- for (int i = 0; i < objects.size(); i++) {
- Value indexValue = instructionIterator.insertConstIntInstruction(code, appView.options(), i);
- Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
- instructionIterator.add(materializingInstruction);
+ var rewriteOptions = appView.options().rewriteArrayOptions();
+ if (rewriteOptions.canUseFilledNewArrayOfNonStringObjects()
+ && objects.size() < rewriteOptions.maxSizeForFilledNewArrayOfReferences) {
+ List<Value> arrayValues = new ArrayList<>(objects.size());
+ for (int i = 0; i < objects.size(); i++) {
+ Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+ instructionIterator.add(materializingInstruction);
+ arrayValues.add(materializingInstruction.outValue());
+ }
instructionIterator.add(
- new ArrayPut(
- MemberType.OBJECT, newObjectsValue, indexValue, materializingInstruction.outValue()));
+ new InvokeNewArray(
+ appView.dexItemFactory().objectArrayType, newObjectsValue, arrayValues));
+ } else {
+ instructionIterator.add(
+ new NewArrayEmpty(newObjectsValue, sizeValue, appView.dexItemFactory().objectArrayType));
+
+ for (int i = 0; i < objects.size(); i++) {
+ Value indexValue =
+ instructionIterator.insertConstIntInstruction(code, appView.options(), i);
+ Instruction materializingInstruction = objects.get(i).buildIR(appView, code);
+ instructionIterator.add(materializingInstruction);
+ instructionIterator.add(
+ new ArrayPut(
+ MemberType.OBJECT,
+ newObjectsValue,
+ indexValue,
+ materializingInstruction.outValue()));
+ }
}
// Pass the newly created `objects` array to RawMessageInfo.<init>(...) or
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index fbe3ff5..00182d3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.StaticGet;
@@ -302,18 +303,30 @@
*/
private static ThrowingIterator<Value, InvalidRawMessageInfoException> createObjectIterator(
Value objectsValue) throws InvalidRawMessageInfoException {
- if (objectsValue.isPhi() || !objectsValue.definition.isNewArrayEmpty()) {
+ if (objectsValue.isPhi()) {
throw new InvalidRawMessageInfoException();
}
NewArrayEmpty newArrayEmpty = objectsValue.definition.asNewArrayEmpty();
- int expectedArraySize = objectsValue.uniqueUsers().size() - 1;
+ InvokeNewArray invokeNewArray = objectsValue.definition.asInvokeNewArray();
- // Verify that the size is correct.
+ if (newArrayEmpty == null && invokeNewArray == null) {
+ throw new InvalidRawMessageInfoException();
+ }
+ // Verify that the array is used in only one spot.
+ if (invokeNewArray != null) {
+ if (!objectsValue.hasSingleUniqueUser()) {
+ throw new InvalidRawMessageInfoException();
+ }
+ return ThrowingIterator.fromIterator(invokeNewArray.inValues().iterator());
+ }
+
Value sizeValue = newArrayEmpty.size().getAliasedValue();
- if (sizeValue.isPhi()
- || !sizeValue.definition.isConstNumber()
- || sizeValue.definition.asConstNumber().getIntValue() != expectedArraySize) {
+ if (sizeValue.isPhi() || !sizeValue.definition.isConstNumber()) {
+ throw new InvalidRawMessageInfoException();
+ }
+ int arraySize = sizeValue.definition.asConstNumber().getIntValue();
+ if (arraySize != objectsValue.uniqueUsers().size() - 1) {
throw new InvalidRawMessageInfoException();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index a214f7c..0a79887 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1921,8 +1921,11 @@
newBlock.getMutableSuccessors().add(this);
newBlock.getMutablePredecessors().add(predecessor);
predecessor.replaceSuccessor(this, newBlock);
- blockIterator.add(newBlock);
- assert newBlock.getNumber() >= 0 : "Number must be assigned by `onNewBlock`";
+ if (blockIterator == null) {
+ code.blocks.add(newBlock);
+ } else {
+ blockIterator.add(newBlock);
+ }
}
// Replace the blocks predecessors with the new ones.
predecessors.clear();
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 1d40f99..afe8e0b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -129,7 +129,7 @@
}
@Override
- public void addThrowingInstructionToPossiblyThrowingBlock(
+ public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
IRCode code,
ListIterator<BasicBlock> blockIterator,
Instruction instruction,
@@ -140,11 +140,15 @@
assert !block.hasCatchHandlers();
assert splitBlock.hasCatchHandlers();
block.copyCatchHandlers(code, blockIterator, splitBlock, options);
- while (IteratorUtils.peekPrevious(blockIterator) != splitBlock) {
- blockIterator.previous();
+ if (blockIterator != null) {
+ while (IteratorUtils.peekPrevious(blockIterator) != splitBlock) {
+ blockIterator.previous();
+ }
}
+ return splitBlock;
} else {
add(instruction);
+ return null;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index ae400dc..8b519ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -207,12 +207,12 @@
}
@Override
- public void addThrowingInstructionToPossiblyThrowingBlock(
+ public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
IRCode code,
ListIterator<BasicBlock> blockIterator,
Instruction instruction,
InternalOptions options) {
- instructionIterator.addThrowingInstructionToPossiblyThrowingBlock(
+ return instructionIterator.addThrowingInstructionToPossiblyThrowingBlock(
code, blockIterator, instruction, options);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
index e06cdbee..99f92eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -73,20 +73,25 @@
* <p>Use with caution!
*/
public static void removeArrayAndTransitiveInputsIfNotUsed(IRCode code, Instruction definition) {
- Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
if (definition.isConstNumber()) {
// No need to explicitly remove `null`, it will be removed by ordinary dead code elimination
// anyway.
assert definition.asConstNumber().isZero();
return;
}
-
- if (definition.isNewArrayEmpty()) {
- Value arrayValue = definition.outValue();
- if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
- return;
- }
-
+ Value arrayValue = definition.outValue();
+ if (arrayValue.hasPhiUsers() || arrayValue.hasDebugUsers()) {
+ return;
+ }
+ if (!definition.isNewArrayEmptyOrInvokeNewArray()) {
+ assert false;
+ return;
+ }
+ Deque<InstructionOrPhi> worklist = new ArrayDeque<>();
+ InvokeNewArray invokeNewArray = definition.asInvokeNewArray();
+ if (invokeNewArray != null) {
+ worklist.add(definition);
+ } else if (definition.isNewArrayEmpty()) {
for (Instruction user : arrayValue.uniqueUsers()) {
// If we encounter an Assume instruction here, we also need to consider indirect users.
assert !user.isAssume();
@@ -95,11 +100,10 @@
}
worklist.add(user);
}
- internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
- return;
+ } else {
+ assert false;
}
-
- assert false;
+ internalRemoveInstructionAndTransitiveInputsIfNotUsed(code, worklist);
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index c99ee11..8892da0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1038,6 +1038,10 @@
return false;
}
+ public boolean isNewArrayEmptyOrInvokeNewArray() {
+ return isNewArrayEmpty() || isInvokeNewArray();
+ }
+
public NewArrayEmpty asNewArrayEmpty() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 224b457..8a69912 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -26,7 +26,7 @@
public interface InstructionListIterator
extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
- void addThrowingInstructionToPossiblyThrowingBlock(
+ BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
IRCode code,
ListIterator<BasicBlock> blockIterator,
Instruction instruction,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index a2e815f..f0715b5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -221,4 +221,9 @@
void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
registry.registerTypeReference(type);
}
+
+ // Returns the number of elements in the array.
+ public int size() {
+ return inValues.size();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index ed95689..819ba43 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -183,12 +183,12 @@
}
@Override
- public void addThrowingInstructionToPossiblyThrowingBlock(
+ public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
IRCode code,
ListIterator<BasicBlock> blockIterator,
Instruction instruction,
InternalOptions options) {
- currentBlockIterator.addThrowingInstructionToPossiblyThrowingBlock(
+ return currentBlockIterator.addThrowingInstructionToPossiblyThrowingBlock(
code, blockIterator, instruction, options);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c6d527f..a829247 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -49,10 +49,6 @@
return super.toString() + " " + type.toString();
}
- public Value dest() {
- return outValue;
- }
-
public Value size() {
return inValues.get(0);
}
@@ -60,7 +56,7 @@
@Override
public void buildDex(DexBuilder builder) {
int size = builder.allocatedRegister(size(), getNumber());
- int dest = builder.allocatedRegister(dest(), getNumber());
+ int dest = builder.allocatedRegister(outValue, getNumber());
builder.add(this, new DexNewArray(dest, size, type));
}
@@ -171,4 +167,10 @@
void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
registry.registerTypeReference(type);
}
+
+ // Returns the size of the array if it is known, -1 otherwise.
+ public int sizeIfConst() {
+ Value size = size();
+ return size.isConstNumber() ? size.getConstInstruction().asConstNumber().getIntValue() : -1;
+ }
}
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 4a29cb6..a091553 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
@@ -148,8 +148,8 @@
/**
* Builder object for constructing high-level IR from dex bytecode.
*
- * <p>The generated IR is in SSA form. The SSA construction is based on the paper
- * "Simple and Efficient Construction of Static Single Assignment Form" available at
+ * <p>The generated IR is in SSA form. The SSA construction is based on the paper "Simple and
+ * Efficient Construction of Static Single Assignment Form" available at
* http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf
*/
public class IRBuilder {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 4c3fc63..99eef8c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
import com.android.tools.r8.cf.code.CfFieldInstruction;
import com.android.tools.r8.cf.code.CfInstanceOf;
import com.android.tools.r8.cf.code.CfInstruction;
@@ -35,11 +36,14 @@
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.synthetic.CheckCastSourceCode;
+import com.android.tools.r8.ir.synthetic.ConstClassSourceCode;
import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
import com.android.tools.r8.ir.synthetic.InstanceOfSourceCode;
import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
+import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.TraversalContinuation;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
@@ -112,6 +116,8 @@
reference = instruction.asCheckCast().getType();
} else if (instruction.isInstanceOf()) {
reference = instruction.asInstanceOf().getType();
+ } else if (instruction.isConstClass()) {
+ reference = instruction.asConstClass().getType();
} else {
return appView.computedMinApiLevel();
}
@@ -119,23 +125,39 @@
return appView.computedMinApiLevel();
}
DexClass holder = appView.definitionFor(reference.getContextType());
- if (holder == null || !holder.isLibraryClass()) {
+ if (holder == null) {
return appView.computedMinApiLevel();
}
- ComputedApiLevel referenceApiLevel =
- apiLevelCompute.computeApiLevelForLibraryReference(reference, ComputedApiLevel.unknown());
+ Pair<DexClass, ComputedApiLevel> classAndApiLevel =
+ reference.isDexType()
+ ? Pair.create(
+ holder,
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ reference, ComputedApiLevel.unknown()))
+ : AndroidApiLevelUtils.findAndComputeApiLevelForLibraryDefinition(
+ appView, appView.appInfoForDesugaring(), holder, reference.asDexMember());
+ ComputedApiLevel referenceApiLevel = classAndApiLevel.getSecond();
if (appView.computedMinApiLevel().isGreaterThanOrEqualTo(referenceApiLevel)
|| isApiLevelLessThanOrEqualTo9(referenceApiLevel)
|| referenceApiLevel.isUnknownApiLevel()) {
return appView.computedMinApiLevel();
}
+ assert referenceApiLevel.isKnownApiLevel();
+ DexClass firstLibraryClass = classAndApiLevel.getFirst();
+ if (firstLibraryClass == null || !firstLibraryClass.isLibraryClass()) {
+ assert false : "When computed a known api level we should always have a library class";
+ return appView.computedMinApiLevel();
+ }
// Check for protected or package private access flags before outlining.
- if (holder.isInterface() || instruction.isCheckCast() || instruction.isInstanceOf()) {
+ if (firstLibraryClass.isInterface()
+ || instruction.isCheckCast()
+ || instruction.isInstanceOf()
+ || instruction.isConstClass()) {
return referenceApiLevel;
} else {
DexEncodedMember<?, ?> definition =
simpleLookupInClassHierarchy(
- holder.asLibraryClass(),
+ firstLibraryClass.asLibraryClass(),
reference.isDexMethod()
? x -> x.lookupMethod(reference.asDexMethod())
: x -> x.lookupField(reference.asDexField()));
@@ -181,7 +203,8 @@
assert instruction.isInvoke()
|| instruction.isFieldInstruction()
|| instruction.isCheckCast()
- || instruction.isInstanceOf();
+ || instruction.isInstanceOf()
+ || instruction.isConstClass();
ProgramMethod outlinedMethod =
ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
@@ -217,6 +240,8 @@
setCodeForCheckCast(syntheticMethodBuilder, instruction.asCheckCast(), factory);
} else if (instruction.isInstanceOf()) {
setCodeForInstanceOf(syntheticMethodBuilder, instruction.asInstanceOf(), factory);
+ } else if (instruction.isConstClass()) {
+ setCodeForConstClass(syntheticMethodBuilder, instruction.asConstClass(), factory);
} else {
assert instruction.isCfInstruction();
setCodeForFieldInstruction(
@@ -315,6 +340,18 @@
.generateCfCode());
}
+ private void setCodeForConstClass(
+ SyntheticMethodBuilder methodBuilder, CfConstClass instruction, DexItemFactory factory) {
+ DexClass target = appView.definitionFor(instruction.getType());
+ assert target != null;
+ methodBuilder
+ .setProto(factory.createProto(factory.classType))
+ .setCode(
+ m ->
+ ConstClassSourceCode.create(appView, m.getHolderType(), target.getType())
+ .generateCfCode());
+ }
+
private boolean verifyLibraryHolderAndInvoke(
DexClass libraryHolder, DexMethod apiMethod, boolean isVirtualInvoke) {
DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarer.java
index bd4b771..5c956aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/disabledesugarer/DesugaredLibraryDisableDesugarer.java
@@ -62,6 +62,7 @@
return rewriteInstruction(instruction, context) != null;
}
+ // TODO(b/261024278): Share this code.
private CfInstruction rewriteInstruction(CfInstruction instruction, ProgramMethod context) {
if (!appView.dexItemFactory().multiDexTypes.contains(context.getHolderType())) {
return null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index 314d657..cae75f2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -15,13 +15,11 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
-import com.google.common.collect.Sets.SetView;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Collectors;
public class HumanRewritingFlags {
@@ -510,7 +508,6 @@
}
public HumanRewritingFlags build() {
- validate();
return new HumanRewritingFlags(
ImmutableMap.copyOf(rewritePrefix),
ImmutableSet.copyOf(dontRewritePrefix),
@@ -533,19 +530,5 @@
ImmutableMap.copyOf(amendLibraryMethod),
ImmutableMap.copyOf(amendLibraryField));
}
-
- private void validate() {
- SetView<DexType> dups =
- Sets.intersection(customConversions.keySet(), wrapperConversions.keySet());
- if (!dups.isEmpty()) {
- throw reporter.fatalError(
- new StringDiagnostic(
- "Invalid desugared library configuration. "
- + "Duplicate types in custom conversions and wrapper conversions: "
- + String.join(
- ", ", dups.stream().map(DexType::toString).collect(Collectors.toSet())),
- origin));
- }
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
new file mode 100644
index 0000000..2daf329
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
@@ -0,0 +1,569 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateVarHandleMethods.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.desugar.varhandle;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
+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.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
+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.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.google.common.collect.ImmutableList;
+
+public final class VarHandleDesugaringMethods {
+
+ public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+ factory.createSynthesizedType("Lcom/android/tools/r8/DesugarVarHandle;");
+ factory.createSynthesizedType("Ljava/lang/reflect/Field;");
+ factory.createSynthesizedType("Lsun/misc/Unsafe;");
+ }
+
+ public static void generateDesugarVarHandleClass(
+ SyntheticProgramClassBuilder builder, DexItemFactory factory) {
+ builder.setInstanceFields(
+ ImmutableList.of(
+ DexEncodedField.syntheticBuilder()
+ .setField(
+ factory.createField(
+ builder.getType(),
+ factory.createType(factory.createString("Lsun/misc/Unsafe;")),
+ factory.createString("U")))
+ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedField.syntheticBuilder()
+ .setField(
+ factory.createField(
+ builder.getType(),
+ factory.createType(factory.createString("Ljava/lang/Class;")),
+ factory.createString("recv")))
+ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedField.syntheticBuilder()
+ .setField(
+ factory.createField(
+ builder.getType(),
+ factory.createType(factory.createString("Ljava/lang/Class;")),
+ factory.createString("type")))
+ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedField.syntheticBuilder()
+ .setField(
+ factory.createField(
+ builder.getType(), factory.longType, factory.createString("offset")))
+ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+ .disableAndroidApiLevelCheck()
+ .build()));
+ DexMethod set =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.voidType, factory.objectType, factory.objectType),
+ factory.createString("set"));
+ DexMethod get =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.objectType, factory.objectType),
+ factory.createString("get"));
+ DexMethod compareAndSetInt =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(
+ factory.booleanType, factory.objectType, factory.intType, factory.intType),
+ factory.createString("compareAndSet"));
+ DexMethod getInt =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.intType, factory.objectType),
+ factory.createString("get"));
+ DexMethod compareAndSet =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(
+ factory.booleanType, factory.objectType, factory.objectType, factory.objectType),
+ factory.createString("compareAndSet"));
+ DexMethod setInt =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.voidType, factory.objectType, factory.intType),
+ factory.createString("set"));
+ DexMethod getLong =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.longType, factory.objectType),
+ factory.createString("get"));
+ DexMethod constructor_3 =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(
+ factory.voidType,
+ factory.createType(factory.createString("Ljava/lang/Class;")),
+ factory.createType(factory.createString("Ljava/lang/String;")),
+ factory.createType(factory.createString("Ljava/lang/Class;"))),
+ factory.createString("<init>"));
+ DexMethod setLong =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.voidType, factory.objectType, factory.longType),
+ factory.createString("set"));
+ DexMethod compareAndSetLong =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(
+ factory.booleanType, factory.objectType, factory.longType, factory.longType),
+ factory.createString("compareAndSet"));
+ builder.setDirectMethods(
+ ImmutableList.of(
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(constructor_3)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true))
+ .setCode(DesugarVarHandle_constructor_3(factory, constructor_3))
+ .disableAndroidApiLevelCheck()
+ .build()));
+ builder.setVirtualMethods(
+ ImmutableList.of(
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(set)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_set(factory, set))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(get)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_get(factory, get))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(compareAndSetInt)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_compareAndSetInt(factory, compareAndSetInt))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(getInt)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_getInt(factory, getInt))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(compareAndSet)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_compareAndSet(factory, compareAndSet))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(setInt)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_setInt(factory, setInt))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(getLong)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_getLong(factory, getLong))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(setLong)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_setLong(factory, setLong))
+ .disableAndroidApiLevelCheck()
+ .build(),
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(compareAndSetLong)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarVarHandle_compareAndSetLong(factory, compareAndSetLong))
+ .disableAndroidApiLevelCheck()
+ .build()));
+ }
+
+ public static void generateDesugarMethodHandlesLookupClass(
+ SyntheticProgramClassBuilder builder, DexItemFactory factory) {
+ builder.setInstanceFields(ImmutableList.of());
+ DexMethod findVarHandle =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(
+ factory.createType(factory.createString("Lcom/android/tools/r8/DesugarVarHandle;")),
+ factory.createType(factory.createString("Ljava/lang/Class;")),
+ factory.createType(factory.createString("Ljava/lang/String;")),
+ factory.createType(factory.createString("Ljava/lang/Class;"))),
+ factory.createString("findVarHandle"));
+ DexMethod constructor_0 =
+ factory.createMethod(
+ builder.getType(),
+ factory.createProto(factory.voidType),
+ factory.createString("<init>"));
+ builder.setDirectMethods(
+ ImmutableList.of(
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(constructor_0)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true))
+ .setCode(DesugarMethodHandlesLookup_constructor_0(factory, constructor_0))
+ .disableAndroidApiLevelCheck()
+ .build()));
+ builder.setVirtualMethods(
+ ImmutableList.of(
+ DexEncodedMethod.syntheticBuilder()
+ .setMethod(findVarHandle)
+ .setAccessFlags(
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+ .setCode(DesugarMethodHandlesLookup_findVarHandle(factory, findVarHandle))
+ .disableAndroidApiLevelCheck()
+ .build()));
+ }
+
+ public static CfCode DesugarMethodHandlesLookup_constructor_0(
+ DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 1,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 183,
+ factory.createMethod(
+ factory.objectType,
+ factory.createProto(factory.voidType),
+ factory.createString("<init>")),
+ false),
+ new CfReturnVoid(),
+ label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarMethodHandlesLookup_findVarHandle(
+ DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 5,
+ 4,
+ ImmutableList.of(
+ label0,
+ new CfNew(factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.OBJECT, 2),
+ new CfLoad(ValueType.OBJECT, 3),
+ new CfInvoke(
+ 183,
+ factory.createMethod(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.createProto(
+ factory.voidType, factory.classType, factory.stringType, factory.classType),
+ factory.createString("<init>")),
+ false),
+ new CfReturn(ValueType.OBJECT),
+ label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_constructor_3(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ CfLabel label4 = new CfLabel();
+ CfLabel label5 = new CfLabel();
+ CfLabel label6 = new CfLabel();
+ CfLabel label7 = new CfLabel();
+ CfLabel label8 = new CfLabel();
+ CfLabel label9 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 4,
+ 6,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 183,
+ factory.createMethod(
+ factory.objectType,
+ factory.createProto(factory.voidType),
+ factory.createString("<init>")),
+ false),
+ label1,
+ new CfConstClass(factory.createType("Lsun/misc/Unsafe;")),
+ new CfConstString(factory.createString("theUnsafe")),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.classType,
+ factory.createProto(
+ factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+ factory.createString("getDeclaredField")),
+ false),
+ new CfStore(ValueType.OBJECT, 4),
+ label2,
+ new CfLoad(ValueType.OBJECT, 4),
+ new CfConstNumber(1, ValueType.INT),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Ljava/lang/reflect/Field;"),
+ factory.createProto(factory.voidType, factory.booleanType),
+ factory.createString("setAccessible")),
+ false),
+ label3,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 4),
+ new CfConstNull(),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Ljava/lang/reflect/Field;"),
+ factory.createProto(factory.objectType, factory.objectType),
+ factory.createString("get")),
+ false),
+ new CfCheckCast(factory.createType("Lsun/misc/Unsafe;")),
+ new CfInstanceFieldWrite(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.createType("Lsun/misc/Unsafe;"),
+ factory.createString("U"))),
+ label4,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfInstanceFieldWrite(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.classType,
+ factory.createString("recv"))),
+ label5,
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.OBJECT, 2),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.classType,
+ factory.createProto(
+ factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+ factory.createString("getDeclaredField")),
+ false),
+ new CfStore(ValueType.OBJECT, 5),
+ label6,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 5),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Ljava/lang/reflect/Field;"),
+ factory.createProto(factory.classType),
+ factory.createString("getType")),
+ false),
+ new CfInstanceFieldWrite(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.classType,
+ factory.createString("type"))),
+ label7,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInstanceFieldRead(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.createType("Lsun/misc/Unsafe;"),
+ factory.createString("U"))),
+ new CfLoad(ValueType.OBJECT, 1),
+ new CfLoad(ValueType.OBJECT, 2),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.classType,
+ factory.createProto(
+ factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+ factory.createString("getDeclaredField")),
+ false),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Lsun/misc/Unsafe;"),
+ factory.createProto(
+ factory.longType, factory.createType("Ljava/lang/reflect/Field;")),
+ factory.createString("objectFieldOffset")),
+ false),
+ new CfInstanceFieldWrite(
+ factory.createField(
+ factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+ factory.longType,
+ factory.createString("offset"))),
+ label8,
+ new CfReturnVoid(),
+ label9),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_compareAndSet(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 4,
+ ImmutableList.of(
+ label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_compareAndSetInt(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 4,
+ ImmutableList.of(
+ label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_compareAndSetLong(
+ DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 6,
+ ImmutableList.of(
+ label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_get(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 2,
+ ImmutableList.of(label0, new CfConstNull(), new CfReturn(ValueType.OBJECT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_getInt(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 2,
+ ImmutableList.of(
+ label0, new CfConstNumber(-1, ValueType.INT), new CfReturn(ValueType.INT), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_getLong(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 2,
+ ImmutableList.of(
+ label0, new CfConstNumber(-1, ValueType.LONG), new CfReturn(ValueType.LONG), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_set(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 0,
+ 3,
+ ImmutableList.of(label0, new CfReturnVoid(), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_setInt(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 0,
+ 3,
+ ImmutableList.of(label0, new CfReturnVoid(), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
+ public static CfCode DesugarVarHandle_setLong(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 0,
+ 4,
+ ImmutableList.of(label0, new CfReturnVoid(), label1),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+}
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 23f0de9..5002bac 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
@@ -16,7 +16,6 @@
import com.android.tools.r8.algorithms.scc.SCC;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessControl;
@@ -55,7 +54,6 @@
import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.ConstClass;
-import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DebugLocalWrite;
@@ -84,6 +82,7 @@
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.LinearFlowInstructionListIterator;
import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -107,9 +106,12 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.RewriteArrayOptions;
import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LazyBox;
import com.android.tools.r8.utils.LongInterval;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ArrayListMultimap;
@@ -138,10 +140,10 @@
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
@@ -162,7 +164,6 @@
FALSE
}
- private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE;
// This constant was determined by experimentation.
private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
@@ -2101,16 +2102,21 @@
}
}
- private short[] computeArrayFilledData(ConstInstruction[] values, int size, int elementSize) {
- if (values == null) {
- return null;
+ private short[] computeArrayFilledData(Value[] values, int size, int elementSize) {
+ for (Value v : values) {
+ if (!v.isConstant()) {
+ return null;
+ }
}
if (elementSize == 1) {
short[] result = new short[(size + 1) / 2];
for (int i = 0; i < size; i += 2) {
- short value = (short) (values[i].asConstNumber().getIntValue() & 0xFF);
+ short value =
+ (short) (values[i].getConstInstruction().asConstNumber().getIntValue() & 0xFF);
if (i + 1 < size) {
- value |= (short) ((values[i + 1].asConstNumber().getIntValue() & 0xFF) << 8);
+ value |=
+ (short)
+ ((values[i + 1].getConstInstruction().asConstNumber().getIntValue() & 0xFF) << 8);
}
result[i / 2] = value;
}
@@ -2120,7 +2126,7 @@
int shortsPerConstant = elementSize / 2;
short[] result = new short[size * shortsPerConstant];
for (int i = 0; i < size; i++) {
- long value = values[i].asConstNumber().getRawValue();
+ long value = values[i].getConstInstruction().asConstNumber().getRawValue();
for (int part = 0; part < shortsPerConstant; part++) {
result[i * shortsPerConstant + part] = (short) ((value >> (16 * part)) & 0xFFFFL);
}
@@ -2128,185 +2134,346 @@
return result;
}
- private ConstInstruction[] computeConstantArrayValues(
- NewArrayEmpty newArray, BasicBlock block, int size) {
- if (size > MAX_FILL_ARRAY_SIZE) {
- return null;
+ private static class FilledArrayConversionInfo {
+
+ Value[] values;
+ List<ArrayPut> arrayPutsToRemove;
+ LinearFlowInstructionListIterator lastArrayPutIterator;
+
+ public FilledArrayConversionInfo(int size) {
+ values = new Value[size];
+ arrayPutsToRemove = new ArrayList<>(size);
}
- ConstInstruction[] values = new ConstInstruction[size];
+ }
+
+ private FilledArrayConversionInfo computeConversionInfo(
+ FilledArrayCandidate candidate, LinearFlowInstructionListIterator it) {
+ NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+ assert it.peekPrevious() == newArrayEmpty;
+ Value arrayValue = newArrayEmpty.outValue();
+ int size = candidate.size;
+
+ // aput-object allows any object for arrays of interfaces, but new-filled-array fails to verify
+ // if types require a cast.
+ // TODO(b/246971330): Check if adding a checked-cast would have the same observable result. E.g.
+ // if aput-object throws a ClassCastException if given an object that does not implement the
+ // desired interface, then we could add check-cast instructions for arguments we're not sure
+ // about.
+ DexType elementType = newArrayEmpty.type.toDimensionMinusOneType(dexItemFactory);
+ boolean needsTypeCheck =
+ !elementType.isPrimitiveType() && elementType != dexItemFactory.objectType;
+
+ FilledArrayConversionInfo info = new FilledArrayConversionInfo(size);
+ Value[] values = info.values;
int remaining = size;
- Set<Instruction> users = newArray.outValue().uniqueUsers();
- Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
- // We allow the array instantiations to cross block boundaries as long as it hasn't encountered
- // an instruction instance that can throw an exception.
- InstructionIterator it = block.iterator();
- it.nextUntil(i -> i == newArray);
- do {
- visitedBlocks.add(block);
- while (it.hasNext()) {
- Instruction instruction = it.next();
- // If we encounter an instruction that can throw an exception we need to bail out of the
- // optimization so that we do not transform half-initialized arrays into fully initialized
- // arrays on exceptional edges. If the block has no handlers it is not observable so
- // we perform the rewriting.
- if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+ Set<Instruction> users = newArrayEmpty.outValue().uniqueUsers();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ BasicBlock block = instruction.getBlock();
+ // If we encounter an instruction that can throw an exception we need to bail out of the
+ // optimization so that we do not transform half-initialized arrays into fully initialized
+ // arrays on exceptional edges. If the block has no handlers it is not observable so
+ // we perform the rewriting.
+ if (block.hasCatchHandlers() && instruction.instructionInstanceCanThrow()) {
+ return null;
+ }
+ if (!users.contains(instruction)) {
+ // If any instruction can transfer control between the new-array and the last array put
+ // then it is not safe to move the new array to the point of the last put.
+ if (block.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
return null;
}
- if (!users.contains(instruction)) {
- continue;
- }
- // If the initialization sequence is broken by another use we cannot use a
- // fill-array-data instruction.
- if (!instruction.isArrayPut()) {
+ continue;
+ }
+ ArrayPut arrayPut = instruction.asArrayPut();
+ // If the initialization sequence is broken by another use we cannot use a fill-array-data
+ // instruction.
+ if (arrayPut == null || arrayPut.array() != arrayValue) {
+ return null;
+ }
+ if (!arrayPut.index().isConstNumber()) {
+ return null;
+ }
+ if (arrayPut.instructionInstanceCanThrow()) {
+ assert false;
+ return null;
+ }
+ int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
+ if (index < 0 || index >= values.length) {
+ return null;
+ }
+ if (values[index] != null) {
+ return null;
+ }
+ Value value = arrayPut.value();
+ if (needsTypeCheck && !value.isAlwaysNull(appView)) {
+ DexType valueDexType = value.getType().asReferenceType().toDexType(dexItemFactory);
+ if (elementType.isArrayType()) {
+ if (elementType != valueDexType) {
+ return null;
+ }
+ } else if (valueDexType.isArrayType()) {
+ // isSubtype asserts for this case.
return null;
- }
- ArrayPut arrayPut = instruction.asArrayPut();
- if (!(arrayPut.value().isConstant() && arrayPut.index().isConstNumber())) {
- return null;
- }
- int index = arrayPut.index().getConstInstruction().asConstNumber().getIntValue();
- if (index < 0 || index >= values.length) {
- return null;
- }
- if (values[index] != null) {
- return null;
- }
- ConstInstruction value = arrayPut.value().getConstInstruction();
- values[index] = value;
- --remaining;
- if (remaining == 0) {
- return values;
+ } else if (valueDexType.isNullValueType()) {
+ // Assume instructions can cause value.isAlwaysNull() == false while the DexType is null.
+ // TODO(b/246971330): Figure out how to write a test in SimplifyArrayConstructionTest
+ // that hits this case.
+ } else {
+ // TODO(b/246971330): When in d8 mode, we might still be able to see if this is true for
+ // library types (which this helper does not do).
+ if (appView.isSubtype(valueDexType, elementType).isPossiblyFalse()) {
+ return null;
+ }
}
}
- BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
- block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
- it = block != null ? block.iterator() : null;
- } while (it != null);
+ info.arrayPutsToRemove.add(arrayPut);
+ values[index] = value;
+ --remaining;
+ if (remaining == 0) {
+ info.lastArrayPutIterator = it;
+ return info;
+ }
+ }
return null;
}
- private boolean allowNewFilledArrayConstruction(Instruction instruction) {
- if (!(instruction instanceof NewArrayEmpty)) {
+ private static class FilledArrayCandidate {
+ final NewArrayEmpty newArrayEmpty;
+ final int size;
+ final boolean encodeAsFilledNewArray;
+
+ public FilledArrayCandidate(
+ NewArrayEmpty newArrayEmpty, int size, boolean encodeAsFilledNewArray) {
+ assert size > 0;
+ this.newArrayEmpty = newArrayEmpty;
+ this.size = size;
+ this.encodeAsFilledNewArray = encodeAsFilledNewArray;
+ }
+
+ public boolean useFilledNewArray() {
+ return encodeAsFilledNewArray;
+ }
+
+ public boolean useFilledArrayData() {
+ return !useFilledNewArray();
+ }
+ }
+
+ private FilledArrayCandidate computeFilledArrayCandiate(
+ Instruction instruction, RewriteArrayOptions options) {
+ NewArrayEmpty newArrayEmpty = instruction.asNewArrayEmpty();
+ if (newArrayEmpty == null) {
+ return null;
+ }
+ if (instruction.getLocalInfo() != null) {
+ return null;
+ }
+ if (!newArrayEmpty.size().isConstant()) {
+ return null;
+ }
+ int size = newArrayEmpty.size().getConstInstruction().asConstNumber().getIntValue();
+ if (!options.isPotentialSize(size)) {
+ return null;
+ }
+ DexType arrayType = newArrayEmpty.type;
+ boolean encodeAsFilledNewArray = canUseFilledNewArray(arrayType, size, options);
+ if (!encodeAsFilledNewArray && !canUseFilledArrayData(arrayType, size, options)) {
+ return null;
+ }
+ return new FilledArrayCandidate(newArrayEmpty, size, encodeAsFilledNewArray);
+ }
+
+ private boolean canUseFilledNewArray(DexType arrayType, int size, RewriteArrayOptions options) {
+ if (size < options.minSizeForFilledNewArray) {
return false;
}
- NewArrayEmpty newArray = instruction.asNewArrayEmpty();
- if (!newArray.size().isConstant()) {
- return false;
- }
- assert newArray.size().isConstNumber();
- int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
- if (size < 1) {
- return false;
- }
- if (newArray.type.isPrimitiveArrayType()) {
+ // filled-new-array is implemented only for int[] and Object[].
+ // For int[], using filled-new-array is usually smaller than filled-array-data.
+ // filled-new-array supports up to 5 registers before it's filled-new-array/range.
+ if (!arrayType.isPrimitiveArrayType()) {
+ if (size > options.maxSizeForFilledNewArrayOfReferences) {
+ return false;
+ }
+ if (arrayType == dexItemFactory.stringArrayType) {
+ return options.canUseFilledNewArrayOfStrings();
+ }
+ if (!options.canUseFilledNewArrayOfNonStringObjects()) {
+ return false;
+ }
+ if (!options.canUseFilledNewArrayOfArrays()
+ && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
+ return false;
+ }
return true;
}
- return newArray.type == dexItemFactory.stringArrayType
- && options.canUseFilledNewArrayOfObjects();
+ if (arrayType == dexItemFactory.intArrayType) {
+ return size <= options.maxSizeForFilledNewArrayOfInts;
+ }
+ return false;
+ }
+
+ private boolean canUseFilledArrayData(DexType arrayType, int size, RewriteArrayOptions options) {
+ // If there is only one element it is typically smaller to generate the array put
+ // instruction instead of fill array data.
+ if (size < options.minSizeForFilledArrayData || size > options.maxSizeForFilledArrayData) {
+ return false;
+ }
+ return arrayType.isPrimitiveArrayType();
}
/**
- * Replace new-array followed by stores of constants to all entries with new-array
- * and fill-array-data / filled-new-array.
+ * Replace new-array followed by stores of constants to all entries with new-array and
+ * fill-array-data / filled-new-array.
+ *
+ * <p>The format of the new-array and its puts must be of the form:
+ *
+ * <pre>
+ * v0 <- new-array T vSize
+ * ...
+ * array-put v0 vValue1 vIndex1
+ * ...
+ * array-put v0 vValueN vIndexN
+ * </pre>
+ *
+ * <p>The flow between the array v0 and its puts must be linear with no other uses of v0 besides
+ * the array-put instructions, thus any no intermediate instruction (... above) must use v0 and
+ * also cannot have catch handlers that would transfer out control (those could then have uses of
+ * v0).
+ *
+ * <p>The allocation of the new-array can itself have catch handlers, in which case, those are to
+ * remain active on the translated code. Translated code can have two forms.
+ *
+ * <p>The first is using the original array allocation and filling in its data if it can be
+ * encoded:
+ *
+ * <pre>
+ * v0 <- new-array T vSize
+ * filled-array-data v0
+ * ...
+ * ...
+ * </pre>
+ *
+ * The data payload is encoded directly in the instruction so no dependencies are needed for
+ * filling the data array. Thus, the fill is inserted at the point of the allocation. If the
+ * allocation has catch handlers its block must be split and the handlers put on the fill
+ * instruction too. This is correct only because there are no exceptional transfers in (...) that
+ * could observe the early initialization of the data.
+ *
+ * <p>The second is using filled-new-array and has the form:
+ *
+ * <pre>
+ * ...
+ * ...
+ * v0 <- filled-new-array T vValue1 ... vValueN
+ * </pre>
+ *
+ * Here the correctness relies on no exceptional transfers in (...) that could observe the missing
+ * allocation of the array. The late allocation ensures that the values are available at
+ * allocation time. If the original allocation has catch handlers then the new allocation needs to
+ * link those too. In general that may require splitting the block twice so that the new
+ * allocation is the single throwing instruction in its block.
*/
public void simplifyArrayConstruction(IRCode code) {
if (options.isGeneratingClassFiles()) {
return;
}
- for (BasicBlock block : code.blocks) {
- // Map from the array value to the number of array put instruction to remove for that value.
- Map<Value, Instruction> instructionToInsertForArray = new HashMap<>();
- Map<Value, Integer> storesToRemoveForArray = new HashMap<>();
- // First pass: identify candidates and insert fill array data instruction.
- InstructionListIterator it = block.listIterator(code);
- while (it.hasNext()) {
- Instruction instruction = it.next();
- if (instruction.getLocalInfo() != null || !allowNewFilledArrayConstruction(instruction)) {
- continue;
+ WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList(code.blocks);
+ while (worklist.hasNext()) {
+ BasicBlock block = worklist.next();
+ simplifyArrayConstructionBlock(block, worklist, code, options);
+ }
+ }
+
+ private void simplifyArrayConstructionBlock(
+ BasicBlock block, WorkList<BasicBlock> worklist, IRCode code, InternalOptions options) {
+ RewriteArrayOptions rewriteOptions = options.rewriteArrayOptions();
+ InstructionListIterator it = block.listIterator(code);
+ while (it.hasNext()) {
+ FilledArrayCandidate candidate = computeFilledArrayCandiate(it.next(), rewriteOptions);
+ if (candidate == null) {
+ continue;
+ }
+ FilledArrayConversionInfo info =
+ computeConversionInfo(
+ candidate, new LinearFlowInstructionListIterator(code, block, it.nextIndex()));
+ if (info == null) {
+ continue;
+ }
+
+ Instruction instructionAfterCandidate = it.peekNext();
+ NewArrayEmpty newArrayEmpty = candidate.newArrayEmpty;
+ DexType arrayType = newArrayEmpty.type;
+ int size = candidate.size;
+ Set<Instruction> instructionsToRemove = SetUtils.newIdentityHashSet(size + 1);
+ if (candidate.useFilledNewArray()) {
+ assert newArrayEmpty.getLocalInfo() == null;
+ Instruction lastArrayPut = info.lastArrayPutIterator.peekPrevious();
+ Value invokeValue = code.createValue(newArrayEmpty.getOutType(), null);
+ InvokeNewArray invoke =
+ new InvokeNewArray(arrayType, invokeValue, Arrays.asList(info.values));
+ invoke.setPosition(lastArrayPut.getPosition());
+ for (Value value : newArrayEmpty.inValues()) {
+ value.removeUser(newArrayEmpty);
}
- NewArrayEmpty newArray = instruction.asNewArrayEmpty();
- int size = newArray.size().getConstInstruction().asConstNumber().getIntValue();
- ConstInstruction[] values = computeConstantArrayValues(newArray, block, size);
- if (values == null) {
- continue;
- }
- if (newArray.type == dexItemFactory.stringArrayType) {
- // Don't replace with filled-new-array if it requires more than 200 consecutive registers.
- if (size > 200) {
- continue;
- }
- List<Value> stringValues = new ArrayList<>(size);
- for (ConstInstruction value : values) {
- stringValues.add(value.outValue());
- }
- Value invokeValue = code.createValue(newArray.getOutType(), newArray.getLocalInfo());
- InvokeNewArray invoke =
- new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
- for (Value value : newArray.inValues()) {
- value.removeUser(newArray);
- }
- newArray.outValue().replaceUsers(invokeValue);
- it.removeOrReplaceByDebugLocalRead();
- instructionToInsertForArray.put(invokeValue, invoke);
- storesToRemoveForArray.put(invokeValue, size);
+ newArrayEmpty.outValue().replaceUsers(invokeValue);
+ instructionsToRemove.add(newArrayEmpty);
+
+ boolean originalAllocationPointHasHandlers = block.hasCatchHandlers();
+ boolean insertionPointHasHandlers = lastArrayPut.getBlock().hasCatchHandlers();
+
+ if (!insertionPointHasHandlers && !originalAllocationPointHasHandlers) {
+ info.lastArrayPutIterator.add(invoke);
} else {
- // If there is only one element it is typically smaller to generate the array put
- // instruction instead of fill array data.
- if (size == 1) {
- continue;
+ BasicBlock insertionBlock = info.lastArrayPutIterator.split(code);
+ if (originalAllocationPointHasHandlers) {
+ if (!insertionBlock.isTrivialGoto()) {
+ BasicBlock blockAfterInsertion = insertionBlock.listIterator(code).split(code);
+ assert insertionBlock.isTrivialGoto();
+ worklist.addIfNotSeen(blockAfterInsertion);
+ }
+ insertionBlock.moveCatchHandlers(block);
+ } else {
+ worklist.addIfNotSeen(insertionBlock);
}
- int elementSize = newArray.type.elementSizeForPrimitiveArrayType();
- short[] contents = computeArrayFilledData(values, size, elementSize);
- if (contents == null) {
- continue;
- }
- if (block.hasCatchHandlers()) {
- continue;
- }
- int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
- NewArrayFilledData fillArray =
- new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents);
- fillArray.setPosition(newArray.getPosition());
- it.add(fillArray);
- storesToRemoveForArray.put(newArray.outValue(), size);
+ insertionBlock.listIterator(code).add(invoke);
+ }
+ } else {
+ assert candidate.useFilledArrayData();
+ int elementSize = arrayType.elementSizeForPrimitiveArrayType();
+ short[] contents = computeArrayFilledData(info.values, size, elementSize);
+ if (contents == null) {
+ continue;
+ }
+ // fill-array-data requires the new-array-empty instruction to remain, as it does not
+ // itself create an array.
+ NewArrayFilledData fillArray =
+ new NewArrayFilledData(newArrayEmpty.outValue(), elementSize, size, contents);
+ fillArray.setPosition(newArrayEmpty.getPosition());
+ BasicBlock newBlock =
+ it.addThrowingInstructionToPossiblyThrowingBlock(code, null, fillArray, options);
+ if (newBlock != null) {
+ worklist.addIfNotSeen(newBlock);
}
}
- // Second pass: remove all the array put instructions for the array for which we have
- // inserted a fill array data instruction instead.
- if (!storesToRemoveForArray.isEmpty()) {
- Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
- do {
- visitedBlocks.add(block);
- it = block.listIterator(code);
- while (it.hasNext()) {
- Instruction instruction = it.next();
- if (instruction.isArrayPut()) {
- Value array = instruction.asArrayPut().array();
- Integer toRemoveCount = storesToRemoveForArray.get(array);
- if (toRemoveCount != null) {
- if (toRemoveCount > 0) {
- storesToRemoveForArray.put(array, --toRemoveCount);
- it.remove();
- }
- if (toRemoveCount == 0) {
- storesToRemoveForArray.put(array, --toRemoveCount);
- Instruction construction = instructionToInsertForArray.get(array);
- if (construction != null) {
- // Set the position of the new array construction to be the position of the
- // last removed put at which point we are now adding the construction.
- construction.setPosition(instruction.getPosition());
- it.add(construction);
- }
- }
- }
+
+ instructionsToRemove.addAll(info.arrayPutsToRemove);
+ Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
+ for (Instruction instruction : instructionsToRemove) {
+ BasicBlock ownerBlock = instruction.getBlock();
+ if (visitedBlocks.add(ownerBlock)) {
+ InstructionListIterator removeIt = ownerBlock.listIterator(code);
+ while (removeIt.hasNext()) {
+ if (instructionsToRemove.contains(removeIt.next())) {
+ removeIt.removeOrReplaceByDebugLocalRead();
}
}
- BasicBlock nextBlock = block.exit().isGoto() ? block.exit().asGoto().getTarget() : null;
- block = nextBlock != null && !visitedBlocks.contains(nextBlock) ? nextBlock : null;
- } while (block != null);
+ }
}
+
+ // The above has invalidated the block iterator so reset it and continue.
+ it = block.listIterator(code, instructionAfterCandidate);
}
- assert code.isConsistentSSA(appView);
}
// TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index a649af6..6fb553c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -18,6 +18,7 @@
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
@@ -68,6 +69,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.MemberType;
@@ -1060,6 +1062,10 @@
instruction.asInstancePut(), code, context, enumClass, enumValue);
case INVOKE_DIRECT:
case INVOKE_INTERFACE:
+ return analyzeInvokeUser(instruction.asInvokeMethod(), code, context, enumClass, enumValue);
+ case INVOKE_NEW_ARRAY:
+ return analyzeInvokeNewArrayUser(
+ instruction.asInvokeNewArray(), code, context, enumClass, enumValue);
case INVOKE_STATIC:
case INVOKE_SUPER:
case INVOKE_VIRTUAL:
@@ -1130,6 +1136,40 @@
return Reason.INVALID_ARRAY_PUT;
}
+ private Reason analyzeInvokeNewArrayUser(
+ InvokeNewArray invokeNewArray,
+ IRCode code,
+ ProgramMethod context,
+ DexProgramClass enumClass,
+ Value enumValue) {
+ // MyEnum[] array = new MyEnum[] { MyEnum.A }; is valid.
+ // We need to prove that the value to put in and the array have correct types.
+ TypeElement arrayType = invokeNewArray.getOutType();
+ assert arrayType.isArrayType();
+
+ ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
+ if (arrayBaseType == null) {
+ assert false;
+ return Reason.INVALID_INVOKE_NEW_ARRAY;
+ }
+ if (arrayBaseType.getClassType() != enumClass.type) {
+ return Reason.INVALID_INVOKE_NEW_ARRAY;
+ }
+
+ for (Value value : invokeNewArray.inValues()) {
+ TypeElement valueBaseType = value.getType();
+ if (valueBaseType.isArrayType()) {
+ assert valueBaseType.asArrayType().getBaseType().isClassType();
+ assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
+ valueBaseType = valueBaseType.asArrayType().getBaseType();
+ }
+ if (!arrayBaseType.equalUpToNullability(valueBaseType)) {
+ return Reason.INVALID_INVOKE_NEW_ARRAY;
+ }
+ }
+ return Reason.ELIGIBLE;
+ }
+
private Reason analyzeCheckCastUser(
CheckCast checkCast,
IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 3e17a0a..7cb0290 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -32,6 +32,8 @@
new StringReason("IMPLICIT_UP_CAST_IN_RETURN");
public static final Reason INVALID_FIELD_PUT = new StringReason("INVALID_FIELD_PUT");
public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
+ public static final Reason INVALID_INVOKE_NEW_ARRAY =
+ new StringReason("INVALID_INVOKE_NEW_ARRAY");
public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
public static final Reason ASSIGNMENT_OUTSIDE_INIT = new StringReason("ASSIGNMENT_OUTSIDE_INIT");
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ConstClassSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ConstClassSourceCode.java
new file mode 100644
index 0000000..b1c04e9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ConstClassSourceCode.java
@@ -0,0 +1,39 @@
+// 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;
+
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code representing a simple call to a const class.
+public final class ConstClassSourceCode extends SyntheticCfCodeProvider {
+
+ private final DexType constClassType;
+
+ private ConstClassSourceCode(AppView<?> appView, DexType holder, DexType constClassType) {
+ super(appView, holder);
+ this.constClassType = constClassType;
+ }
+
+ public static ConstClassSourceCode create(
+ AppView<?> appView, DexType holder, DexType checkCastType) {
+ return new ConstClassSourceCode(appView, holder, checkCastType);
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ List<CfInstruction> instructions = new ArrayList<>();
+ instructions.add(new CfConstClass(constClassType));
+ instructions.add(new CfReturn(ValueType.OBJECT));
+ return standardCfCodeFromInstructions(instructions);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
index 57257ff..3f32d83 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
@@ -15,7 +15,7 @@
import java.util.ArrayList;
import java.util.List;
-// Source code representing a simple call to CheckCast.
+// Source code representing a simple call to instance-of.
public final class InstanceOfSourceCode extends SyntheticCfCodeProvider {
private final DexType instanceOfType;
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 3e4078b..6e14907 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -424,12 +425,16 @@
// Perform a conservative evaluation of an array content of dex type values from its construction
// until its use at a given instruction.
- private static DexType[] evaluateTypeArrayContentFromConstructionToUse(
- NewArrayEmpty newArray,
- List<CheckCast> aliases,
- int size,
- Instruction user,
- DexItemFactory factory) {
+ private static DexTypeList evaluateTypeArrayContentFromConstructionToUse(
+ NewArrayEmpty newArray, List<CheckCast> aliases, Instruction user, DexItemFactory factory) {
+ int size = newArray.sizeIfConst();
+ if (size < 0) {
+ return null;
+ } else if (size == 0) {
+ // TODO: We should likely still scan to ensure no ArrayPut instructions exist.
+ return DexTypeList.empty();
+ }
+
DexType[] values = new DexType[size];
int remaining = size;
Set<Instruction> users = Sets.newIdentityHashSet();
@@ -453,7 +458,7 @@
if (instruction == user) {
// Return the array content if all elements are known when hitting the user for which
// the content was requested.
- return remaining == 0 ? values : null;
+ return remaining == 0 ? new DexTypeList(values) : null;
}
// Any other kinds of use besides array-put mean that the array escapes and its content
// could be altered.
@@ -498,6 +503,21 @@
return null;
}
+ private static DexTypeList evaluateTypeArrayContent(
+ InvokeNewArray newArray, DexItemFactory factory) {
+ List<Value> arrayValues = newArray.inValues();
+ int size = arrayValues.size();
+ DexType[] values = new DexType[size];
+ for (int i = 0; i < size; ++i) {
+ DexType type = getTypeFromConstClassOrBoxedPrimitive(arrayValues.get(i), factory);
+ if (type == null) {
+ return null;
+ }
+ values[i] = type;
+ }
+ return new DexTypeList(values);
+ }
+
/**
* Visits all {@link ArrayPut}'s with the given {@param classListValue} as array and {@link Class}
* as value. Then collects all corresponding {@link DexType}s so as to determine reflective cases.
@@ -551,30 +571,13 @@
}
// Make sure this Value refers to a new array.
- if (!classListValue.definition.isNewArrayEmpty()
- || !classListValue.definition.asNewArrayEmpty().size().isConstant()) {
+ if (classListValue.definition.isNewArrayEmpty()) {
+ return evaluateTypeArrayContentFromConstructionToUse(
+ classListValue.definition.asNewArrayEmpty(), aliases, invoke, factory);
+ } else if (classListValue.definition.isInvokeNewArray()) {
+ return evaluateTypeArrayContent(classListValue.definition.asInvokeNewArray(), factory);
+ } else {
return null;
}
-
- int size =
- classListValue
- .definition
- .asNewArrayEmpty()
- .size()
- .getConstInstruction()
- .asConstNumber()
- .getIntValue();
- if (size == 0) {
- return DexTypeList.empty();
- }
-
- DexType[] arrayContent =
- evaluateTypeArrayContentFromConstructionToUse(
- classListValue.definition.asNewArrayEmpty(), aliases, size, invoke, factory);
-
- if (arrayContent == null) {
- return null;
- }
- return new DexTypeList(arrayContent);
}
}
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index e5d00a9..40ee16e 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
import com.android.tools.r8.graph.InnerClassAttribute;
@@ -135,7 +136,11 @@
// For fields and methods that cannot be found the chance of recovering by repackaging is
// pretty slim thus we allow for repackaging the callers.
if (resolutionResult.isFieldResolutionResult()) {
- assert resolutionResult.asFieldResolutionResult().isFailedResolution();
+ FieldResolutionResult fieldResolutionResult = resolutionResult.asFieldResolutionResult();
+ assert fieldResolutionResult.isFailedResolution()
+ || (fieldResolutionResult.isMultiFieldResolutionResult()
+ && fieldResolutionResult.getProgramField() != null
+ && fieldResolutionResult.getProgramField().getDefinition().isPrivate());
return;
}
MethodResolutionResult methodResult = resolutionResult.asMethodResolutionResult();
diff --git a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
index 662f9f4..d779378 100644
--- a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -7,7 +7,9 @@
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.dex.code.CfOrDexInstruction;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
@@ -15,11 +17,13 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevelUtils;
import java.util.ListIterator;
public class ComputeApiLevelUseRegistry extends UseRegistry<ProgramMethod> {
protected final AppView<?> appView;
+ private final AppInfoWithClassHierarchy appInfoWithClassHierarchy;
private final AndroidApiLevelCompute apiLevelCompute;
private final boolean isEnabled;
private ComputedApiLevel maxApiReferenceLevel;
@@ -28,6 +32,7 @@
AppView<?> appView, ProgramMethod context, AndroidApiLevelCompute apiLevelCompute) {
super(appView, context);
this.appView = appView;
+ this.appInfoWithClassHierarchy = appView.appInfoForDesugaring();
this.apiLevelCompute = apiLevelCompute;
isEnabled = apiLevelCompute.isEnabled();
maxApiReferenceLevel = appView.computedMinApiLevel();
@@ -148,10 +153,28 @@
private void setMaxApiReferenceLevel(DexReference reference) {
if (isEnabled) {
- maxApiReferenceLevel =
- maxApiReferenceLevel.max(
- apiLevelCompute.computeApiLevelForLibraryReference(
- reference, apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
+ if (reference.isDexType()) {
+ maxApiReferenceLevel =
+ maxApiReferenceLevel.max(
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ reference, apiLevelCompute.getPlatformApiLevelOrUnknown(appView)));
+ } else if (!reference.getContextType().isClassType()) {
+ maxApiReferenceLevel = maxApiReferenceLevel.max(appView.computedMinApiLevel());
+ } else {
+ DexClass holder = appView.definitionFor(reference.getContextType());
+ ComputedApiLevel referenceApiLevel = ComputedApiLevel.unknown();
+ if (holder != null) {
+ referenceApiLevel =
+ AndroidApiLevelUtils.findAndComputeApiLevelForLibraryDefinition(
+ appView, appInfoWithClassHierarchy, holder, reference.asDexMember())
+ .getSecond();
+ }
+ maxApiReferenceLevel =
+ maxApiReferenceLevel.max(
+ referenceApiLevel.isUnknownApiLevel()
+ ? apiLevelCompute.getPlatformApiLevelOrUnknown(appView)
+ : referenceApiLevel);
+ }
}
}
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 213819d..6c5c07b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -41,6 +41,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexDefinition;
@@ -104,7 +105,9 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
@@ -146,6 +149,7 @@
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.OptionalBool;
@@ -1092,15 +1096,24 @@
DexField field, ProgramMethod context, boolean isRead, boolean isReflective) {
FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
if (info == null) {
- DexEncodedField encodedField = resolveField(field, context).getResolvedField();
+ Box<DexClassAndField> seenResult = new Box<>();
+ resolveField(field, context)
+ .forEachSuccessfulFieldResolutionResult(
+ singleResolutionResult -> {
+ DexClassAndField resolutionPair = singleResolutionResult.getResolutionPair();
+ if (!seenResult.isSet() || resolutionPair.isProgramField()) {
+ seenResult.set(resolutionPair);
+ }
+ });
// If the field does not exist, then record this in the mapping, such that we don't have to
// resolve the field the next time.
- if (encodedField == null) {
+ if (!seenResult.isSet()) {
fieldAccessInfoCollection.extend(field, MISSING_FIELD_ACCESS_INFO);
return true;
}
+ DexEncodedField encodedField = seenResult.get().getDefinition();
info = getOrCreateFieldAccessInfo(encodedField);
// If `field` is an indirect reference, then create a mapping for it, such that we don't have
@@ -5079,25 +5092,43 @@
return;
}
Value parametersValue = constructorDefinition.inValues().get(1);
- if (parametersValue.isPhi() || !parametersValue.definition.isNewArrayEmpty()) {
+ if (parametersValue.isPhi()) {
// Give up, we can't tell which constructor is being invoked.
return;
}
-
- Value parametersSizeValue = parametersValue.definition.asNewArrayEmpty().size();
- if (parametersSizeValue.isPhi() || !parametersSizeValue.definition.isConstNumber()) {
- // Give up, we can't tell which constructor is being invoked.
+ NewArrayEmpty newArrayEmpty = parametersValue.definition.asNewArrayEmpty();
+ InvokeNewArray invokeNewArray = parametersValue.definition.asInvokeNewArray();
+ int parametersSize =
+ newArrayEmpty != null
+ ? newArrayEmpty.sizeIfConst()
+ : invokeNewArray != null ? invokeNewArray.size() : -1;
+ if (parametersSize < 0) {
return;
}
ProgramMethod initializer = null;
- int parametersSize = parametersSizeValue.definition.asConstNumber().getIntValue();
if (parametersSize == 0) {
initializer = clazz.getProgramDefaultInitializer();
} else {
DexType[] parameterTypes = new DexType[parametersSize];
int missingIndices = parametersSize;
+
+ if (newArrayEmpty != null) {
+ missingIndices = parametersSize;
+ } else {
+ missingIndices = 0;
+ List<Value> values = invokeNewArray.inValues();
+ for (int i = 0; i < parametersSize; ++i) {
+ DexType type =
+ ConstantValueUtils.getDexTypeRepresentedByValueForTracing(values.get(i), appView);
+ if (type == null) {
+ return;
+ }
+ parameterTypes[i] = type;
+ }
+ }
+
for (Instruction user : parametersValue.uniqueUsers()) {
if (user.isArrayPut()) {
ArrayPut arrayPutInstruction = user.asArrayPut();
@@ -5159,20 +5190,31 @@
}
Value interfacesValue = invoke.arguments().get(1);
- if (interfacesValue.isPhi() || !interfacesValue.definition.isNewArrayEmpty()) {
+ if (interfacesValue.isPhi()) {
// Give up, we can't tell which interfaces the proxy implements.
return;
}
- WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
- for (Instruction user : interfacesValue.uniqueUsers()) {
- if (!user.isArrayPut()) {
- continue;
+ InvokeNewArray invokeNewArray = interfacesValue.definition.asInvokeNewArray();
+ NewArrayEmpty newArrayEmpty = interfacesValue.definition.asNewArrayEmpty();
+ List<Value> values;
+ if (invokeNewArray != null) {
+ values = invokeNewArray.inValues();
+ } else if (newArrayEmpty != null) {
+ values = new ArrayList<>(interfacesValue.uniqueUsers().size());
+ for (Instruction user : interfacesValue.uniqueUsers()) {
+ ArrayPut arrayPut = user.asArrayPut();
+ if (arrayPut != null) {
+ values.add(arrayPut.value());
+ }
}
+ } else {
+ return;
+ }
- ArrayPut arrayPut = user.asArrayPut();
- DexType type =
- ConstantValueUtils.getDexTypeRepresentedByValueForTracing(arrayPut.value(), appView);
+ WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
+ for (Value value : values) {
+ DexType type = ConstantValueUtils.getDexTypeRepresentedByValueForTracing(value, appView);
if (type == null || !type.isClassType()) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
index 2635b95..00f905e 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -403,6 +403,7 @@
InternalOptions getInternalOptions() {
InternalOptions options = new InternalOptions();
options.loadAllClassDefinitions = true;
+ options.lookupLibraryBeforeProgram = true;
TraceReferencesConsumer consumer = getConsumer();
DumpOptions.Builder builder =
DumpOptions.builder(Tool.TraceReferences)
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 7369593..0a8b899 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
@@ -22,6 +23,9 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.Set;
public class AndroidApiLevelUtils {
@@ -191,4 +195,109 @@
return oldBaseLibraryClass != null
&& isApiSafeForReference(newBaseLibraryClass, oldBaseLibraryClass, appView);
}
+
+ public static Pair<DexClass, ComputedApiLevel> findAndComputeApiLevelForLibraryDefinition(
+ AppView<?> appView,
+ AppInfoWithClassHierarchy appInfo,
+ DexClass holder,
+ DexMember<?, ?> reference) {
+ AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
+ if (holder.isLibraryClass()) {
+ return Pair.create(
+ holder,
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ reference, ComputedApiLevel.unknown()));
+ }
+ // The API database do not allow for resolving into it (since that is not stable), and it is
+ // therefore designed in a way where all members of classes can be queried on any sub-type with
+ // the api level for where it is reachable. It is therefore sufficient for us, to figure out if
+ // an instruction is a library call, to either find a program definition or to find the library
+ // frontier.
+ // Scan through the type hierarchy to find the first library class or program definition.
+ DexClass firstClassWithReferenceOrLibraryClass =
+ firstLibraryClassOrProgramClassWithDefinition(appInfo, holder, reference);
+ if (firstClassWithReferenceOrLibraryClass == null) {
+ return Pair.create(null, ComputedApiLevel.unknown());
+ }
+ if (!firstClassWithReferenceOrLibraryClass.isLibraryClass()) {
+ return Pair.create(firstClassWithReferenceOrLibraryClass, appView.computedMinApiLevel());
+ }
+ ComputedApiLevel apiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ reference.withHolder(
+ firstClassWithReferenceOrLibraryClass.getType(), appView.dexItemFactory()),
+ ComputedApiLevel.unknown());
+ if (apiLevel.isKnownApiLevel()) {
+ return Pair.create(firstClassWithReferenceOrLibraryClass, apiLevel);
+ }
+ // We were unable to find a definition in the class hierarchy, check all interfaces for a
+ // definition or the library interfaces for the first interface definition.
+ Set<DexClass> firstLibraryInterfaces =
+ findAllFirstLibraryInterfacesOrProgramClassWithDefinition(appInfo, holder, reference);
+ if (firstLibraryInterfaces.size() == 1) {
+ DexClass firstClass = firstLibraryInterfaces.iterator().next();
+ if (!firstClass.isLibraryClass()) {
+ return Pair.create(firstClass, appView.computedMinApiLevel());
+ }
+ }
+ DexClass foundClass = null;
+ ComputedApiLevel minApiLevel = ComputedApiLevel.unknown();
+ for (DexClass libraryInterface : firstLibraryInterfaces) {
+ assert libraryInterface.isLibraryClass();
+ ComputedApiLevel libraryIfaceApiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ reference.withHolder(
+ firstClassWithReferenceOrLibraryClass.getType(), appView.dexItemFactory()),
+ ComputedApiLevel.unknown());
+ if (minApiLevel.isGreaterThan(libraryIfaceApiLevel)) {
+ minApiLevel = libraryIfaceApiLevel;
+ foundClass = libraryInterface;
+ }
+ }
+ return Pair.create(foundClass, minApiLevel);
+ }
+
+ private static DexClass firstLibraryClassOrProgramClassWithDefinition(
+ AppInfoWithClassHierarchy appInfo, DexClass originalClass, DexMember<?, ?> reference) {
+ if (originalClass.isLibraryClass()) {
+ return originalClass;
+ }
+ WorkList<DexClass> workList = WorkList.newIdentityWorkList(originalClass);
+ while (workList.hasNext()) {
+ DexClass clazz = workList.next();
+ if (clazz.isLibraryClass()) {
+ return clazz;
+ } else if (clazz.lookupMember(reference) != null) {
+ return clazz;
+ } else if (clazz.getSuperType() != null) {
+ appInfo
+ .contextIndependentDefinitionForWithResolutionResult(clazz.getSuperType())
+ .forEachClassResolutionResult(workList::addIfNotSeen);
+ }
+ }
+ return null;
+ }
+
+ private static Set<DexClass> findAllFirstLibraryInterfacesOrProgramClassWithDefinition(
+ AppInfoWithClassHierarchy appInfo, DexClass originalClass, DexMember<?, ?> reference) {
+ Set<DexClass> interfaces = Sets.newLinkedHashSet();
+ WorkList<DexClass> workList = WorkList.newIdentityWorkList(originalClass);
+ while (workList.hasNext()) {
+ DexClass clazz = workList.next();
+ if (clazz.isLibraryClass()) {
+ if (clazz.isInterface()) {
+ interfaces.add(clazz);
+ }
+ } else if (clazz.lookupMember(reference) != null) {
+ return Collections.singleton(clazz);
+ } else {
+ clazz.forEachImmediateSupertype(
+ superType ->
+ appInfo
+ .contextIndependentDefinitionForWithResolutionResult(superType)
+ .forEachClassResolutionResult(workList::addIfNotSeen));
+ }
+ }
+ return interfaces;
+ }
}
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 3b87985..4f8669b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -816,6 +816,7 @@
public boolean debug = false;
+ private final RewriteArrayOptions rewriteArrayOptions = new RewriteArrayOptions();
private final CallSiteOptimizationOptions callSiteOptimizationOptions =
new CallSiteOptimizationOptions();
private final CfCodeAnalysisOptions cfCodeAnalysisOptions = new CfCodeAnalysisOptions();
@@ -850,6 +851,10 @@
public LineNumberOptimization lineNumberOptimization = LineNumberOptimization.ON;
+ public RewriteArrayOptions rewriteArrayOptions() {
+ return rewriteArrayOptions;
+ }
+
public CallSiteOptimizationOptions callSiteOptimizationOptions() {
return callSiteOptimizationOptions;
}
@@ -1383,6 +1388,51 @@
System.getProperty("com.android.tools.r8.lambdaClassFieldsNotFinal") == null;
}
+ public class RewriteArrayOptions {
+ // Arbitrary limit of number of inputs to new-filled-array/range.
+ // The technical limit is 255 (Constants.U8BIT_MAX).
+ public int minSizeForFilledNewArray = 1;
+ public int maxSizeForFilledNewArrayOfReferences = 200;
+ public int maxSizeForFilledNewArrayOfInts = 5;
+
+ // Arbitrary limits of number of inputs to fill-array-data.
+ public int minSizeForFilledArrayData = 2;
+ public int maxSizeForFilledArrayData = 8 * 1024;
+
+ // Check the most relaxed size range allowed.
+ public boolean isPotentialSize(int size) {
+ return minSizeForFilledNewArray <= size && size <= maxSizeForFilledArrayData;
+ }
+
+ // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
+ // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
+ // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
+ // removed during the jelly-bean release cycle and is not there from kitkat.
+ //
+ // Buggy code that accidentally call code that only works on primitives arrays.
+ //
+ // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
+ public boolean canUseFilledNewArrayOfStrings() {
+ assert isGeneratingDex();
+ return hasFeaturePresentFrom(AndroidApiLevel.K);
+ }
+
+ // When adding support for emitting new-filled-array for non-String types, ART 6.0.1 had issues.
+ // https://ci.chromium.org/ui/p/r8/builders/ci/linux-android-6.0.1/6507/overview
+ // It somehow had a new-array-filled return null.
+ public boolean canUseFilledNewArrayOfNonStringObjects() {
+ assert isGeneratingDex();
+ return hasFeaturePresentFrom(AndroidApiLevel.N);
+ }
+
+ // Dalvik doesn't handle new-filled-array with arrays as values. It fails with:
+ // W(629880) VFY: [Ljava/lang/Integer; is not instance of Ljava/lang/Integer; (dalvikvm)
+ public boolean canUseFilledNewArrayOfArrays() {
+ assert isGeneratingDex();
+ return hasFeaturePresentFrom(AndroidApiLevel.L);
+ }
+ }
+
public class CallSiteOptimizationOptions {
private boolean enabled = true;
@@ -2424,19 +2474,6 @@
return hasFeaturePresentFrom(AndroidApiLevel.J);
}
- // Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
- // arrays of objects. This is unfortunate, since this never hits arm devices, but we have
- // to disallow filled-new-array of objects for dalvik until kitkat. The buggy code was
- // removed during the jelly-bean release cycle and is not there from kitkat.
- //
- // Buggy code that accidentally call code that only works on primitives arrays.
- //
- // https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
- public boolean canUseFilledNewArrayOfObjects() {
- assert isGeneratingDex();
- return hasFeaturePresentFrom(AndroidApiLevel.K);
- }
-
// Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
// where an aget-wide instruction using the same register for the array
// and the first register of the result could lead to the wrong exception
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
index 50d23c9..7d41ae1 100644
--- a/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingIterator.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
@@ -32,4 +33,18 @@
}
return result;
}
+
+ public static <T, E extends Exception> ThrowingIterator<T, E> fromIterator(Iterator<T> it) {
+ return new ThrowingIterator<>() {
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public T next() throws E {
+ return it.next();
+ }
+ };
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
index d83d7e0..6e540b8 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
@@ -14,12 +14,12 @@
import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
import com.android.tools.r8.graph.DexDebugEventBuilder;
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
import com.android.tools.r8.graph.DexDebugPositionState;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -55,6 +55,10 @@
this.processedEvents = processedEvents;
}
+ public boolean didEmitLineEvents() {
+ return startLine != -1;
+ }
+
private void emitAdvancePc(int pc) {
processedEvents.add(new AdvancePC(pc - previousPc));
previousPc = pc;
@@ -183,24 +187,25 @@
}
};
- for (DexDebugEvent event : debugInfo.events) {
- event.accept(visitor);
- }
-
- // If we only have one line event we can always retrace back uniquely.
- if (mappedPositions.size() <= 1
- && !hasOverloads
- && !appView.options().debug
- && appView.options().lineNumberOptimization != LineNumberOptimization.OFF
- && appView.options().allowDiscardingResidualDebugInfo()
- && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
- dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
- return mappedPositions;
+ DexDebugEvent[] events = debugInfo.events;
+ if (events.length > 0) {
+ SetPositionFrame preambleFrame = getAsPreambleFrame(events[0]);
+ if (preambleFrame != null) {
+ // The preamble is specially identified here as it is active at method entry and thus not
+ // part of the instruction stream events.
+ Position position = preambleFrame.getPosition();
+ Position newPosition =
+ PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions);
+ processedEvents.add(appView.dexItemFactory().createPositionFrame(newPosition));
+ }
+ for (int i = (preambleFrame == null) ? 0 : 1; i < events.length; i++) {
+ events[i].accept(visitor);
+ }
}
EventBasedDebugInfo optimizedDebugInfo =
new EventBasedDebugInfo(
- positionEventEmitter.getStartLine(),
+ positionEventEmitter.didEmitLineEvents() ? positionEventEmitter.getStartLine() : 0,
debugInfo.parameters,
processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
@@ -212,6 +217,16 @@
return mappedPositions;
}
+ private SetPositionFrame getAsPreambleFrame(DexDebugEvent event) {
+ SetPositionFrame positionFrame = event.asSetPositionFrame();
+ if (positionFrame != null
+ && positionFrame.getPosition().isSyntheticPosition()
+ && positionFrame.getPosition().getLine() == 0) {
+ return positionFrame;
+ }
+ return null;
+ }
+
// This conversion *always* creates an event based debug info encoding as any non-info will
// be created as an implicit PC encoding.
private static EventBasedDebugInfo getEventBasedDebugInfo(
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
index f29f9cb..890cb80 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.utils.positions;
import com.android.tools.r8.debuginfo.DebugRepresentation;
-import com.android.tools.r8.dex.code.DexInstruction;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugEvent;
@@ -13,7 +12,6 @@
import com.android.tools.r8.graph.DexDebugEventVisitor;
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
import com.android.tools.r8.graph.DexDebugPositionState;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.ProgramMethod;
@@ -23,7 +21,6 @@
import com.android.tools.r8.ir.code.Position.OutlinePosition;
import com.android.tools.r8.ir.code.Position.PositionBuilder;
import com.android.tools.r8.ir.code.Position.SourcePosition;
-import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.positions.PositionToMappedRangeMapper.PcBasedDebugInfoRecorder;
@@ -49,7 +46,6 @@
EventBasedDebugInfo debugInfo =
getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
IntBox firstDefaultEventPc = new IntBox(-1);
- BooleanBox singleOriginalLine = new BooleanBox(true);
Pair<Integer, Position> lastPosition = new Pair<>();
DexDebugEventVisitor visitor =
new DexDebugPositionState(
@@ -64,10 +60,6 @@
}
Position currentPosition = getPositionFromPositionState(this);
if (lastPosition.getSecond() != null) {
- if (singleOriginalLine.isTrue()
- && !currentPosition.equals(lastPosition.getSecond())) {
- singleOriginalLine.set(false);
- }
remapAndAddForPc(
pcBasedDebugInfo,
lastPosition.getFirst(),
@@ -86,20 +78,6 @@
event.accept(visitor);
}
- // If the method has a single non-preamble line, check that the preamble is not active on any
- // throwing instruction before the single line becomes active.
- if (singleOriginalLine.isTrue() && firstDefaultEventPc.get() > 0) {
- for (DexInstruction instruction : dexCode.instructions) {
- if (instruction.getOffset() < firstDefaultEventPc.get()) {
- if (instruction.canThrow()) {
- singleOriginalLine.set(false);
- }
- } else {
- break;
- }
- }
- }
-
int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(dexCode).getOffset();
if (lastPosition.getSecond() != null) {
remapAndAddForPc(
@@ -112,14 +90,7 @@
}
assert !mappedPositions.isEmpty() || dexCode.instructions.length == 1;
- if (singleOriginalLine.isTrue()
- && lastPosition.getSecond() != null
- && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
- dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
- pcBasedDebugInfo.recordSingleLineFor(method, pcEncodingCutoff);
- } else {
- pcBasedDebugInfo.recordPcMappingFor(method, pcEncodingCutoff);
- }
+ pcBasedDebugInfo.recordPcMappingFor(method, pcEncodingCutoff);
return mappedPositions;
}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
index ae0eb0e..13f013e 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
@@ -291,17 +290,10 @@
lastPosition = currentPosition;
}
}
- Range obfuscatedRange;
- if (definition.getCode().isDexCode()
- && definition.getCode().asDexCode().getDebugInfo()
- == DexDebugInfoForSingleLineMethod.getInstance()) {
- assert firstPosition.getOriginalLine() == lastPosition.getOriginalLine();
- obfuscatedRange = nonCardinalRangeCache.get(0, MAX_LINE_NUMBER);
- } else {
- obfuscatedRange =
- nonCardinalRangeCache.get(
- firstPosition.getObfuscatedLine(), lastPosition.getObfuscatedLine());
- }
+ Range obfuscatedRange =
+ nonCardinalRangeCache.get(
+ firstPosition.getObfuscatedLine(), lastPosition.getObfuscatedLine());
+
MappedRange lastMappedRange =
getMappedRangesForPosition(
appView,
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
index e4cb801..914b174 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
@@ -8,8 +8,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugInfo;
-import com.android.tools.r8.graph.DexDebugInfoForSingleLineMethod;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.ProgramMethod;
import java.util.ArrayList;
import java.util.HashMap;
@@ -45,7 +43,7 @@
pcBasedDebugInfoRecorder =
appView.options().canUseNativeDexPcInsteadOfDebugInfo()
? new NativePcSupport()
- : new Pc2PcMappingSupport(appView.options().allowDiscardingResidualDebugInfo());
+ : new Pc2PcMappingSupport();
noPcMapper = new DexPositionToNoPcMappedRangeMapper(appView);
pcMapper = new DexPositionToPcMappedRangeMapper(appView, pcBasedDebugInfoRecorder);
}
@@ -57,17 +55,9 @@
boolean hasOverloads,
boolean canUseDexPc,
int pcEncodingCutoff) {
- List<MappedPosition> mappedPositions =
- canUseDexPc
- ? pcMapper.optimizeDexCodePositionsForPc(method, positionRemapper, pcEncodingCutoff)
- : noPcMapper.optimizeDexCodePositions(method, positionRemapper, hasOverloads);
- DexEncodedMethod definition = method.getDefinition();
- if (definition.getCode().isDexCode()
- && definition.getCode().asDexCode().getDebugInfo()
- == DexDebugInfoForSingleLineMethod.getInstance()) {
- pcBasedDebugInfoRecorder.recordSingleLineFor(method, pcEncodingCutoff);
- }
- return mappedPositions;
+ return canUseDexPc
+ ? pcMapper.optimizeDexCodePositionsForPc(method, positionRemapper, pcEncodingCutoff)
+ : noPcMapper.optimizeDexCodePositions(method, positionRemapper, hasOverloads);
}
@Override
@@ -80,9 +70,6 @@
/** Callback to record a code object with a given max instruction PC and parameter count. */
void recordPcMappingFor(ProgramMethod method, int maxEncodingPc);
- /** Callback to record a code object with only a single "line". */
- void recordSingleLineFor(ProgramMethod method, int maxEncodingPc);
-
/**
* Install the correct debug info objects.
*
@@ -124,29 +111,12 @@
private final List<UpdateInfo> codesToUpdate = new ArrayList<>();
- // We can only drop single-line debug info if it is OK to lose the source-file info.
- // This list is null if we must retain single-line entries.
- private final List<DexCode> singleLineCodesToClear;
-
- public Pc2PcMappingSupport(boolean allowDiscardingSourceFile) {
- singleLineCodesToClear = allowDiscardingSourceFile ? new ArrayList<>() : null;
- }
-
@Override
public int getPcEncoding(int pc) {
assert pc >= 0;
return pc + 1;
}
- private boolean cantAddToClearSet(ProgramMethod method) {
- assert method.getDefinition().getCode().isDexCode();
- if (singleLineCodesToClear == null) {
- return true;
- }
- singleLineCodesToClear.add(method.getDefinition().getCode().asDexCode());
- return false;
- }
-
@Override
public void recordPcMappingFor(ProgramMethod method, int maxEncodingPc) {
assert method.getDefinition().getCode().isDexCode();
@@ -157,13 +127,6 @@
}
@Override
- public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
- if (cantAddToClearSet(method)) {
- recordPcMappingFor(method, maxEncodingPc);
- }
- }
-
- @Override
public void updateDebugInfoInCodeObjects() {
Map<UpdateInfo, DexDebugInfo> debugInfos = new HashMap<>();
codesToUpdate.forEach(
@@ -175,9 +138,6 @@
assert debugInfo.asPcBasedInfo().getMaxPc() == entry.maxEncodingPc;
entry.code.setDebugInfo(debugInfo);
});
- if (singleLineCodesToClear != null) {
- singleLineCodesToClear.forEach(c -> c.setDebugInfo(null));
- }
}
private static DexDebugInfo buildPc2PcDebugInfo(UpdateInfo info) {
@@ -204,11 +164,6 @@
}
@Override
- public void recordSingleLineFor(ProgramMethod method, int maxEncodingPc) {
- clearDebugInfo(method);
- }
-
- @Override
public void updateDebugInfoInCodeObjects() {
// Already null out the info so nothing to do.
}
diff --git a/src/test/examplesJava9/varhandle/ArrayOfInt.java b/src/test/examplesJava9/varhandle/ArrayOfInt.java
new file mode 100644
index 0000000..dc5ea71
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/ArrayOfInt.java
@@ -0,0 +1,29 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class ArrayOfInt {
+
+ public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+ VarHandle arrayVarHandle = MethodHandles.arrayElementVarHandle(int[].class);
+ int[] array = new int[2];
+ arrayVarHandle.set(array, 0, 1);
+ System.out.println((int) arrayVarHandle.get(array, 0));
+ System.out.println((int) arrayVarHandle.get(array, 1));
+ arrayVarHandle.compareAndSet(array, 1, 1, 3);
+ System.out.println((int) arrayVarHandle.get(array, 0));
+ System.out.println((int) arrayVarHandle.get(array, 1));
+ arrayVarHandle.compareAndSet(array, 1, 0, 2);
+ System.out.println((int) arrayVarHandle.get(array, 0));
+ System.out.println((int) arrayVarHandle.get(array, 1));
+ // TODO(sgjesse): Handle boxed.
+ // arrayVarHandle.compareAndSet(array, 1, 2, box(3));
+ arrayVarHandle.compareAndSet(array, 1, 2, 3);
+ System.out.println((int) arrayVarHandle.get(array, 0));
+ System.out.println((int) arrayVarHandle.get(array, 1));
+ }
+}
diff --git a/src/test/examplesJava9/varhandle/ArrayOfLong.java b/src/test/examplesJava9/varhandle/ArrayOfLong.java
new file mode 100644
index 0000000..5d3e550
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/ArrayOfLong.java
@@ -0,0 +1,29 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class ArrayOfLong {
+
+ public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+ VarHandle arrayVarHandle = MethodHandles.arrayElementVarHandle(long[].class);
+ long[] array = new long[2];
+ arrayVarHandle.set(array, 0, 1L);
+ System.out.println((long) arrayVarHandle.get(array, 0));
+ System.out.println((long) arrayVarHandle.get(array, 1));
+ arrayVarHandle.compareAndSet(array, 1, 1L, 3L);
+ System.out.println((long) arrayVarHandle.get(array, 0));
+ System.out.println((long) arrayVarHandle.get(array, 1));
+ arrayVarHandle.compareAndSet(array, 1, 0L, 2L);
+ System.out.println((long) arrayVarHandle.get(array, 0));
+ System.out.println((long) arrayVarHandle.get(array, 1));
+ // TODO(sgjesse): Handle boxed.
+ // arrayVarHandle.compareAndSet(array, 1, 2, box(3));
+ arrayVarHandle.compareAndSet(array, 1, 2L, 3L);
+ System.out.println((long) arrayVarHandle.get(array, 0));
+ System.out.println((long) arrayVarHandle.get(array, 1));
+ }
+}
diff --git a/src/test/examplesJava9/varhandle/InstanceIntField.java b/src/test/examplesJava9/varhandle/InstanceIntField.java
new file mode 100644
index 0000000..0320030
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/InstanceIntField.java
@@ -0,0 +1,223 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class InstanceIntField {
+
+ private int field;
+
+ private static void checkJavaLangInvokeWrongMethodTypeException(RuntimeException e) {
+ if (e.getClass().getCanonicalName().equals("java.lang.invoke.WrongMethodTypeException")
+ || e.getMessage().equals("java.lang.invoke.WrongMethodTypeException")) {
+ return;
+ }
+ throw e;
+ }
+
+ public static void testSet(VarHandle varHandle) {
+ System.out.println("testGet");
+
+ InstanceIntField instance = new InstanceIntField();
+ System.out.println((int) varHandle.get(instance));
+
+ // int and Integer values.
+ varHandle.set(instance, (int) 1);
+ System.out.println((int) varHandle.get(instance));
+ varHandle.set(instance, Integer.valueOf(2));
+ System.out.println(varHandle.get(instance));
+
+ // int and Integer compatible values.
+ varHandle.set(instance, (byte) 3);
+ System.out.println((int) varHandle.get(instance));
+ varHandle.set(instance, Byte.valueOf((byte) 4));
+ System.out.println((int) varHandle.get(instance));
+ varHandle.set(instance, '0');
+ System.out.println((int) varHandle.get(instance));
+ varHandle.set(instance, Character.valueOf('1'));
+ System.out.println((int) varHandle.get(instance));
+ varHandle.set(instance, (short) 5);
+ System.out.println((int) varHandle.get(instance));
+ varHandle.set(instance, Short.valueOf((short) 6));
+ System.out.println((int) varHandle.get(instance));
+
+ // int and Integer non-compatible values.
+ try {
+ varHandle.set(instance, true);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.set(instance, 3L);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.set(instance, Long.valueOf(3));
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.set(instance, "3");
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.set(instance, 3.0f);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.set(instance, 3.0);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ }
+
+ public static void testCompareAndSet(VarHandle varHandle) {
+ System.out.println("testCompareAndSet");
+
+ InstanceIntField instance = new InstanceIntField();
+
+ // int and Integer values.
+ varHandle.compareAndSet(instance, 1, 2);
+ System.out.println((int) varHandle.get(instance));
+ varHandle.compareAndSet(instance, 0, 1);
+ System.out.println((int) varHandle.get(instance));
+ varHandle.compareAndSet(instance, Integer.valueOf(1), 2);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, 2, Integer.valueOf(3));
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, Integer.valueOf(3), Integer.valueOf(4));
+ System.out.println(varHandle.get(instance));
+
+ // int and Integer compatible values.
+ varHandle.compareAndSet(instance, (byte) 4, 5);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, 5, (byte) 6);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, (byte) 6, (byte) 7);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, Byte.valueOf((byte) 7), (byte) 8);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, (byte) 8, Byte.valueOf((byte) 9));
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, Byte.valueOf((byte) 9), Byte.valueOf((byte) 10));
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, 10, '0');
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, '0', 49);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, '1', '2');
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, (byte) 50, '3');
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, '3', (byte) 52);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, '4', '5');
+ System.out.println(varHandle.get(instance));
+
+ // int and Integer non-compatible values.
+ try {
+ varHandle.compareAndSet(instance, 6L, 7L);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6L, 7);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6, Long.valueOf(7));
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, Long.valueOf(6), 7);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, Long.valueOf(6), Long.valueOf(7));
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6, 7.0f);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6.0f, 7);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6.0f, 7.0f);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6, 7.0);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6.0, 7);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, 6.0, 7.0);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+
+ try {
+ varHandle.compareAndSet(instance, 6, "7");
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, "6", 7);
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ try {
+ varHandle.compareAndSet(instance, "6", "7");
+ } catch (RuntimeException e) {
+ checkJavaLangInvokeWrongMethodTypeException(e);
+ System.out.println(varHandle.get(instance));
+ }
+ }
+
+ public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+ VarHandle varHandle =
+ MethodHandles.lookup().findVarHandle(InstanceIntField.class, "field", int.class);
+ testSet(varHandle);
+ testCompareAndSet(varHandle);
+ }
+}
diff --git a/src/test/examplesJava9/varhandle/InstanceLongField.java b/src/test/examplesJava9/varhandle/InstanceLongField.java
new file mode 100644
index 0000000..dcca70e
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/InstanceLongField.java
@@ -0,0 +1,40 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class InstanceLongField {
+
+ private long field;
+
+ public static void testSet(VarHandle varHandle) {
+ System.out.println("testGet");
+
+ InstanceLongField instance = new InstanceLongField();
+
+ System.out.println(varHandle.get(instance));
+ varHandle.set(instance, 1);
+ System.out.println(varHandle.get(instance));
+ }
+
+ public static void testCompareAndSet(VarHandle varHandle) {
+ System.out.println("testCompareAndSet");
+
+ InstanceLongField instance = new InstanceLongField();
+
+ varHandle.compareAndSet(instance, 1, 2);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, 0, 1);
+ System.out.println(varHandle.get(instance));
+ }
+
+ public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+ VarHandle varHandle =
+ MethodHandles.lookup().findVarHandle(InstanceLongField.class, "field", long.class);
+ testSet(varHandle);
+ testCompareAndSet(varHandle);
+ }
+}
diff --git a/src/test/examplesJava9/varhandle/InstanceObjectField.java b/src/test/examplesJava9/varhandle/InstanceObjectField.java
new file mode 100644
index 0000000..e7b4970
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/InstanceObjectField.java
@@ -0,0 +1,40 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class InstanceObjectField {
+
+ private Object field;
+
+ public static void testSet(VarHandle varHandle) {
+ System.out.println("testGet");
+
+ InstanceObjectField instance = new InstanceObjectField();
+
+ System.out.println(varHandle.get(instance));
+ varHandle.set(instance, 1);
+ System.out.println(varHandle.get(instance));
+ }
+
+ public static void testCompareAndSet(VarHandle varHandle) {
+ System.out.println("testCompareAndSet");
+
+ InstanceObjectField instance = new InstanceObjectField();
+
+ varHandle.compareAndSet(instance, 0, 1);
+ System.out.println(varHandle.get(instance));
+ varHandle.compareAndSet(instance, null, 1);
+ System.out.println(varHandle.get(instance));
+ }
+
+ public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+ VarHandle varHandle =
+ MethodHandles.lookup().findVarHandle(InstanceObjectField.class, "field", Object.class);
+ testSet(varHandle);
+ testCompareAndSet(varHandle);
+ }
+}
diff --git a/src/test/examplesJava9/varhandle/InstanceStringField.java b/src/test/examplesJava9/varhandle/InstanceStringField.java
new file mode 100644
index 0000000..1be6f67
--- /dev/null
+++ b/src/test/examplesJava9/varhandle/InstanceStringField.java
@@ -0,0 +1,45 @@
+// 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 varhandle;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+public class InstanceStringField {
+
+ private Object field;
+
+ private static void println(String s) {
+ System.out.println(s);
+ }
+
+ public static void testSet(VarHandle varHandle) {
+ System.out.println("testGet");
+
+ InstanceStringField instance = new InstanceStringField();
+
+ // Then polymorphic invoke will remove the cast and make that as the return type of the get.
+ println((String) varHandle.get(instance));
+ varHandle.set(instance, "1");
+ println((String) varHandle.get(instance));
+ }
+
+ public static void testCompareAndSet(VarHandle varHandle) {
+ System.out.println("testCompareAndSet");
+
+ InstanceStringField instance = new InstanceStringField();
+
+ varHandle.compareAndSet(instance, 0, "1");
+ println((String) varHandle.get(instance));
+ varHandle.compareAndSet(instance, null, "1");
+ println((String) varHandle.get(instance));
+ }
+
+ public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
+ VarHandle varHandle =
+ MethodHandles.lookup().findVarHandle(InstanceStringField.class, "field", Object.class);
+ testSet(varHandle);
+ testCompareAndSet(varHandle);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index dd84b92..c62a34c 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -153,13 +153,6 @@
return withDexRuntimeFilter(vm -> vm == runtime);
}
- /** Add all available CF runtimes between {@param startInclusive} and {@param endInclusive}. */
- public TestParametersBuilder withDexRuntimes(
- DexVm.Version startInclusive, DexVm.Version endInclusive) {
- return withDexRuntimeFilter(
- vm -> startInclusive.isOlderThanOrEqual(vm) && vm.isOlderThanOrEqual(endInclusive));
- }
-
/** Add all available DEX runtimes that support native multidex. */
public TestParametersBuilder withNativeMultidexDexRuntimes() {
return withDexRuntimesStartingFromIncluding(DexVm.Version.V5_1_1);
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
index f859e30..3c2923d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
@@ -20,6 +20,7 @@
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.apimodel.ApiModelingTestHelper.ApiModelingMethodVerificationHelper;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -103,7 +104,7 @@
.apply(this::setupTestBuilder)
.compile()
.apply(this::setupRunEnvironment)
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, false))
.run(parameters.getRuntime(), Main.class)
.apply(result -> checkOutput(result, false));
}
@@ -115,7 +116,7 @@
.apply(this::setupTestBuilder)
.compile()
.apply(this::setupRunEnvironment)
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, false))
.run(parameters.getRuntime(), Main.class)
.apply(result -> checkOutput(result, false));
}
@@ -127,7 +128,7 @@
.addKeepMainRule(Main.class)
.addKeepClassAndMembersRules(ProgramJoiner.class)
.compile()
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, true))
.apply(this::setupRunEnvironment)
.run(parameters.getRuntime(), Main.class)
.apply(result -> checkOutput(result, true));
@@ -155,17 +156,25 @@
result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
}
- private void inspect(CodeInspector inspector) throws Exception {
- // TODO(b/254510678): We should outline the call to ProgramJoiner.foo.
- verifyThat(
+ private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
+ ApiModelingMethodVerificationHelper verifyHelper =
+ verifyThat(
inspector,
parameters,
Reference.method(
- Reference.classFromClass(ProgramJoiner.class),
+ // TODO(b/254510678): Due to member rebinding, we rebind ProgramJoiner.foo() to
+ // LibraryClass.foo().
+ Reference.classFromClass(isR8 ? LibraryClass.class : ProgramJoiner.class),
"foo",
Collections.emptyList(),
- null))
- .isNotOutlinedFrom(Main.class.getDeclaredMethod("main", String[].class));
+ null));
+ if (isR8 && parameters.isCfRuntime()) {
+ verifyHelper.isOutlinedFromUntil(
+ Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
+ } else {
+ verifyHelper.isOutlinedFromUntilAlsoForCf(
+ Main.class.getDeclaredMethod("main", String[].class), classMethodApiLevel);
+ }
}
// Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
index a0cc571..f6a0a49 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
@@ -20,6 +20,7 @@
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.apimodel.ApiModelingTestHelper.ApiModelingMethodVerificationHelper;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -93,7 +94,7 @@
.apply(this::setupRunEnvironment)
.run(parameters.getRuntime(), Main.class)
.apply(result -> checkOutput(result, false))
- .inspect(this::inspect);
+ .inspect(inspector -> inspect(inspector, false));
}
@Test
@@ -105,7 +106,7 @@
.apply(this::setupRunEnvironment)
.run(parameters.getRuntime(), Main.class)
.apply(result -> checkOutput(result, false))
- .inspect(this::inspect);
+ .inspect(inspector -> inspect(inspector, false));
}
@Test
@@ -115,7 +116,7 @@
.addKeepMainRule(Main.class)
.addKeepClassAndMembersRules(ProgramJoiner.class)
.compile()
- .inspect(this::inspect)
+ .inspect(inspector -> inspect(inspector, true))
.apply(this::setupRunEnvironment)
.run(parameters.getRuntime(), Main.class)
.apply(result -> checkOutput(result, true));
@@ -137,17 +138,25 @@
result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
}
- private void inspect(CodeInspector inspector) throws Exception {
- // TODO(b/254510678): We should outline the call to ProgramClass.foo.
- verifyThat(
+ private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
+ ApiModelingMethodVerificationHelper verifyHelper =
+ verifyThat(
inspector,
parameters,
Reference.method(
- Reference.classFromClass(ProgramJoiner.class),
+ // TODO(b/254510678): Due to member rebinding, we rebind ProgramJoiner.foo() to
+ // LibraryClass.foo().
+ Reference.classFromClass(isR8 ? LibraryClass.class : ProgramJoiner.class),
"foo",
Collections.emptyList(),
- null))
- .isNotOutlinedFrom(Main.class.getDeclaredMethod("main", String[].class));
+ null));
+ if (isR8 && parameters.isCfRuntime()) {
+ verifyHelper.isOutlinedFromUntil(
+ Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
+ } else {
+ verifyHelper.isOutlinedFromUntilAlsoForCf(
+ Main.class.getDeclaredMethod("main", String[].class), mockApiLevel);
+ }
}
// Only present from api level 23.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java
new file mode 100644
index 0000000..64a3180
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java
@@ -0,0 +1,138 @@
+// 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 ApiModelOutlineConstClassTest extends TestBase {
+
+ private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+
+ private static final String[] EXPECTED = new String[] {"class " + typeName(LibraryClass.class)};
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+ testBuilder
+ .addLibraryClasses(LibraryClass.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addProgramClasses(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addAndroidBuildVersion(getApiLevelForRuntime())
+ .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+ .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+ }
+
+ public AndroidApiLevel getApiLevelForRuntime() {
+ return parameters.isCfRuntime()
+ ? AndroidApiLevel.B
+ : parameters.getRuntime().asDex().maxSupportedApiLevel();
+ }
+
+ public boolean addToBootClasspath() {
+ return getApiLevelForRuntime().isGreaterThanOrEqualTo(classApiLevel);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+ testForJvm()
+ .addProgramClasses(Main.class)
+ .addAndroidBuildVersion(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8Debug() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .setMode(CompilationMode.DEBUG)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8Release() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .setMode(CompilationMode.RELEASE)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::setupTestBuilder)
+ .addKeepMainRule(Main.class)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ private void inspect(CodeInspector inspector) throws Exception {
+ verifyThat(inspector, parameters, LibraryClass.class)
+ .hasConstClassOutlinedFromUntil(
+ Main.class.getMethod("main", String[].class), classApiLevel);
+ }
+
+ private void checkOutput(SingleTestRunResult<?> runResult) {
+ if (addToBootClasspath()) {
+ runResult.assertSuccessWithOutputLines(EXPECTED);
+ } else {
+ runResult.assertSuccessWithOutputLines("Not checking instance of");
+ }
+ }
+
+ // Only present from api 23.
+ public static class LibraryClass {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (AndroidBuildVersion.VERSION >= 23) {
+ System.out.println(LibraryClass.class);
+ } else {
+ System.out.println("Not checking instance of");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java
index 3e56179..19971c3 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineSubTypeStaticReferenceTest.java
@@ -7,9 +7,6 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationMode;
@@ -23,8 +20,8 @@
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.lang.reflect.Method;
+import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -133,13 +130,14 @@
Method otherMethod = Sub.class.getMethod("otherMethod");
Method libraryMethod = LibraryClass.class.getMethod("foo");
// TODO(b/254510678): R8 should not member-rebind to a potential non-existing method.
- if (isR8) {
- MethodSubject method = inspector.method(otherMethod);
- assertThat(method, isPresent());
- assertThat(method, invokesMethod(Reference.methodFromMethod(libraryMethod)));
- }
- // TODO(b/254510678): We should outline this up until library api level.
- verifyThat(inspector, parameters, libraryMethod).isNotOutlinedFrom(otherMethod);
+ verifyThat(
+ inspector,
+ parameters,
+ isR8
+ ? Reference.methodFromMethod(libraryMethod)
+ : Reference.method(
+ Reference.classFromClass(Sub.class), "foo", Collections.emptyList(), null))
+ .isOutlinedFromUntil(Sub.class.getDeclaredMethod("otherMethod"), libraryApiLevel);
}
private void checkResultOnBootClassPath(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index e437943..b487caf 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -5,8 +5,6 @@
package com.android.tools.r8.apimodel;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsCheckCast;
-import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsInstanceOf;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
@@ -39,7 +37,9 @@
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
+import org.hamcrest.Matcher;
public abstract class ApiModelingTestHelper {
@@ -272,35 +272,11 @@
}
public void hasCheckCastOutlinedFrom(Method method) {
- // Check that we call is in a synthetic class with a check cast
- ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
- List<FoundMethodSubject> outlinedMethod =
- inspector.allClasses().stream()
- .filter(
- clazz ->
- clazz
- .getOriginalName()
- .startsWith(
- SyntheticItemsTestUtils.syntheticApiOutlineClassPrefix(
- method.getDeclaringClass())))
- .flatMap(clazz -> clazz.allMethods().stream())
- .filter(
- methodSubject ->
- methodSubject.isSynthetic()
- && containsCheckCast(classOfInterestReference).matches(methodSubject))
- .collect(Collectors.toList());
- assertFalse(outlinedMethod.isEmpty());
- // Assert that method invokes the outline
- MethodSubject caller = inspector.method(method);
- assertThat(caller, isPresent());
- assertThat(caller, invokesMethod(outlinedMethod.get(0)));
+ hasOutlinedInstructionWithClassReference(method, CodeMatchers::containsCheckCast);
}
public void hasNotCheckCastOutlinedFrom(Method method) {
- ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
- MethodSubject caller = inspector.method(method);
- assertThat(caller, isPresent());
- assertThat(caller, containsCheckCast(classOfInterestReference));
+ hasNotOulinedInstructionWithClassReference(method, CodeMatchers::containsCheckCast);
}
void hasInstanceOfOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
@@ -312,8 +288,34 @@
}
public void hasInstanceOfOutlinedFrom(Method method) {
+ hasOutlinedInstructionWithClassReference(method, CodeMatchers::containsInstanceOf);
+ }
+
+ public void hasNotInstanceOfOutlinedFrom(Method method) {
+ hasNotOulinedInstructionWithClassReference(method, CodeMatchers::containsInstanceOf);
+ }
+
+ void hasConstClassOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
+ if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
+ hasConstClassOutlinedFrom(method);
+ } else {
+ hasNotConstClassOutlinedFrom(method);
+ }
+ }
+
+ public void hasConstClassOutlinedFrom(Method method) {
+ hasOutlinedInstructionWithClassReference(method, CodeMatchers::containsConstClass);
+ }
+
+ public void hasNotConstClassOutlinedFrom(Method method) {
+ hasNotOulinedInstructionWithClassReference(method, CodeMatchers::containsConstClass);
+ }
+
+ private void hasOutlinedInstructionWithClassReference(
+ Method method, Function<ClassReference, Matcher<? super MethodSubject>> matcher) {
// Check that we call is in a synthetic class with an instance of.
ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+ Matcher<? super MethodSubject> instructionMatcher = matcher.apply(classOfInterestReference);
List<FoundMethodSubject> outlinedMethod =
inspector.allClasses().stream()
.filter(
@@ -326,8 +328,7 @@
.flatMap(clazz -> clazz.allMethods().stream())
.filter(
methodSubject ->
- methodSubject.isSynthetic()
- && containsInstanceOf(classOfInterestReference).matches(methodSubject))
+ methodSubject.isSynthetic() && instructionMatcher.matches(methodSubject))
.collect(Collectors.toList());
assertFalse(outlinedMethod.isEmpty());
// Assert that method invokes the outline
@@ -336,11 +337,12 @@
assertThat(caller, invokesMethod(outlinedMethod.get(0)));
}
- public void hasNotInstanceOfOutlinedFrom(Method method) {
+ private void hasNotOulinedInstructionWithClassReference(
+ Method method, Function<ClassReference, Matcher<? super MethodSubject>> matcher) {
ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
MethodSubject caller = inspector.method(method);
assertThat(caller, isPresent());
- assertThat(caller, containsInstanceOf(classOfInterestReference));
+ assertThat(caller, matcher.apply(classOfInterestReference));
}
}
@@ -447,6 +449,14 @@
}
}
+ void isOutlinedFromUntilAlsoForCf(Executable method, AndroidApiLevel apiLevel) {
+ if (parameters.getApiLevel().isLessThan(apiLevel)) {
+ isOutlinedFrom(method);
+ } else {
+ isNotOutlinedFrom(method);
+ }
+ }
+
void isOutlinedFrom(Executable method) {
// Check that the call is in a synthetic class.
List<FoundMethodSubject> outlinedMethod =
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java
new file mode 100644
index 0000000..af62516
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java
@@ -0,0 +1,34 @@
+// 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.cf.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringArrayOfIntTest extends VarHandleDesugaringTestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("1", "0", "1", "0", "1", "2", "1", "3");
+ private static final String MAIN_CLASS = VarHandle.ArrayOfInt.typeName();
+ private static final String JAR_ENTRY = "varhandle/ArrayOfInt.class";
+
+ @Override
+ protected String getMainClass() {
+ return MAIN_CLASS;
+ }
+
+ @Override
+ protected String getJarEntry() {
+ return JAR_ENTRY;
+ }
+
+ @Override
+ protected String getExpectedOutput() {
+ return EXPECTED_OUTPUT;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java
new file mode 100644
index 0000000..46e130f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java
@@ -0,0 +1,34 @@
+// 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.cf.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringArrayOfLongTest extends VarHandleDesugaringTestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("1", "0", "1", "0", "1", "2", "1", "3");
+ private static final String MAIN_CLASS = VarHandle.ArrayOfLong.typeName();
+ private static final String JAR_ENTRY = "varhandle/ArrayOfLong.class";
+
+ @Override
+ protected String getMainClass() {
+ return MAIN_CLASS;
+ }
+
+ @Override
+ protected String getJarEntry() {
+ return JAR_ENTRY;
+ }
+
+ @Override
+ protected String getExpectedOutput() {
+ return EXPECTED_OUTPUT;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
new file mode 100644
index 0000000..4d15a0a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
@@ -0,0 +1,87 @@
+// 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.cf.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringInstanceIntFieldTest extends VarHandleDesugaringTestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines(
+ "testGet",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "48",
+ "49",
+ "5",
+ "6",
+ "6",
+ "6",
+ "6",
+ "6",
+ "6",
+ "6",
+ "testCompareAndSet",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "10",
+ "48",
+ "49",
+ "50",
+ "51",
+ "52",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53",
+ "53");
+ private static final String MAIN_CLASS = VarHandle.InstanceIntField.typeName();
+ private static final String JAR_ENTRY = "varhandle/InstanceIntField.class";
+
+ @Override
+ protected String getMainClass() {
+ return MAIN_CLASS;
+ }
+
+ @Override
+ protected String getKeepRules() {
+ return "-keep class " + getMainClass() + "{ <fields>; }";
+ }
+
+ @Override
+ protected String getJarEntry() {
+ return JAR_ENTRY;
+ }
+
+ @Override
+ protected String getExpectedOutput() {
+ return EXPECTED_OUTPUT;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
new file mode 100644
index 0000000..0584f1c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
@@ -0,0 +1,39 @@
+// 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.cf.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringInstanceLongFieldTest extends VarHandleDesugaringTestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("testGet", "0", "1", "testCompareAndSet", "0", "1");
+ private static final String MAIN_CLASS = VarHandle.InstanceLongField.typeName();
+ private static final String JAR_ENTRY = "varhandle/InstanceLongField.class";
+
+ @Override
+ protected String getMainClass() {
+ return MAIN_CLASS;
+ }
+
+ @Override
+ protected String getKeepRules() {
+ return "-keep class " + getMainClass() + "{ <fields>; }";
+ }
+
+ @Override
+ protected String getJarEntry() {
+ return JAR_ENTRY;
+ }
+
+ @Override
+ protected String getExpectedOutput() {
+ return EXPECTED_OUTPUT;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
new file mode 100644
index 0000000..b9b2b8e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
@@ -0,0 +1,39 @@
+// 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.cf.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringInstanceObjectFieldTest extends VarHandleDesugaringTestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("testGet", "null", "1", "testCompareAndSet", "null", "1");
+ private static final String MAIN_CLASS = VarHandle.InstanceObjectField.typeName();
+ private static final String JAR_ENTRY = "varhandle/InstanceObjectField.class";
+
+ @Override
+ protected String getMainClass() {
+ return MAIN_CLASS;
+ }
+
+ @Override
+ protected String getKeepRules() {
+ return "-keep class " + getMainClass() + "{ <fields>; }";
+ }
+
+ @Override
+ protected String getJarEntry() {
+ return JAR_ENTRY;
+ }
+
+ @Override
+ protected String getExpectedOutput() {
+ return EXPECTED_OUTPUT;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java
new file mode 100644
index 0000000..dfd32cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java
@@ -0,0 +1,39 @@
+// 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.cf.varhandle;
+
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class VarHandleDesugaringInstanceStringFieldTest extends VarHandleDesugaringTestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("testGet", "null", "1", "testCompareAndSet", "null", "1");
+ private static final String MAIN_CLASS = VarHandle.InstanceStringField.typeName();
+ private static final String JAR_ENTRY = "varhandle/InstanceStringField.class";
+
+ @Override
+ protected String getMainClass() {
+ return MAIN_CLASS;
+ }
+
+ @Override
+ protected String getKeepRules() {
+ return "-keep class " + getMainClass() + "{ <fields>; }";
+ }
+
+ @Override
+ protected String getJarEntry() {
+ return JAR_ENTRY;
+ }
+
+ @Override
+ protected String getExpectedOutput() {
+ return EXPECTED_OUTPUT;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
new file mode 100644
index 0000000..8a5bb2c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
@@ -0,0 +1,108 @@
+// 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.cf.varhandle;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8TestBuilder;
+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.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.examples.jdk9.VarHandle;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils;
+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 abstract class VarHandleDesugaringTestBase extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .build();
+ }
+
+ protected abstract String getMainClass();
+
+ protected String getKeepRules() {
+ return "";
+ }
+
+ protected abstract String getJarEntry();
+
+ protected abstract String getExpectedOutput();
+
+ @Test
+ public void testReference() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramFiles(VarHandle.jar())
+ .run(parameters.getRuntime(), getMainClass())
+ .assertSuccessWithOutput(getExpectedOutput());
+ }
+
+ @Test
+ public void testD8() throws Throwable {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ // Use android.jar from Android T to get the VarHandle type. This is not strictly needed
+ // to D8 as it does not fail on missing types.
+ // TODO(b/247076137): With desugaring removing VarHandle the type should not be needed in
+ // the library and any android.jar should work.
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), getMainClass())
+ // TODO(b/247076137): Test should pass on all platforms with desugaring implemented.
+ .applyIf(
+ // VarHandle is available from Android 9, even though it was not a public API until 13.
+ parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V7_0_0),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.P)
+ || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0),
+ r -> r.assertFailure(),
+ r -> r.assertSuccessWithOutput(getExpectedOutput()));
+ }
+
+ @Test
+ public void testR8() throws Throwable {
+ testForR8(parameters.getBackend())
+ // Use android.jar from Android T to get the VarHandle type.
+ // TODO(b/247076137): With desugaring removing VarHandle the type should not be needed in
+ // the library and any android.jar should work.
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(getMainClass())
+ .addKeepRules(getKeepRules())
+ .applyIf(
+ parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+ R8TestBuilder::allowDiagnosticWarningMessages)
+ .run(parameters.getRuntime(), getMainClass())
+ .applyIf(
+ // VarHandle is available from Android 9, even though it was not a public API until 13.
+ parameters.isDexRuntime()
+ && parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V7_0_0),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ parameters.isDexRuntime()
+ && (parameters.getApiLevel().isLessThan(AndroidApiLevel.P)
+ || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0)),
+ r -> r.assertFailure(),
+ r -> r.assertSuccessWithOutput(getExpectedOutput()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
index c706f50..0630571 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
@@ -363,7 +363,7 @@
codePrinter.visitMethod(generatedMethodName, method.getCode().asCfCode());
index++;
}
- codePrinter.getImports().forEach(imports::addImport);
+ codePrinter.getImportsSorted().forEach(imports::addImport);
return createCfCodeMethodNames;
}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 28ab9a7..0cd6b23 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -8,30 +8,65 @@
import com.android.tools.r8.cf.CfCodePrinter;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
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.JarApplicationReader;
import com.android.tools.r8.graph.JarClassFileReader;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
+import com.beust.jcommander.internal.Sets;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
import java.util.TreeSet;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
public abstract class MethodGenerationBase extends CodeGenerationBase {
protected abstract List<Class<?>> getMethodTemplateClasses();
+ protected List<Class<?>> getClassesToGenerate() {
+ return ImmutableList.of();
+ }
+
+ protected DexEncodedField getField(DexEncodedField field) {
+ return field;
+ }
+
+ protected boolean includeMethod(DexEncodedMethod method) {
+ return !method.isInstanceInitializer();
+ }
+
protected CfCode getCode(String holderName, String methodName, CfCode code) {
return code;
}
+ protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
+ return method;
+ }
+
// Running this method will regenerate / overwrite the content of the generated class.
protected void generateMethodsAndWriteThemToFile() throws IOException {
FileUtils.writeToFile(getGeneratedFile(), null, generateMethods().getBytes());
@@ -43,31 +78,47 @@
File tempFile = File.createTempFile("output-", ".java");
- readMethodTemplatesInto(codePrinter);
- generateRawOutput(codePrinter, tempFile.toPath());
+ Map<DexEncodedMethod, String> generatedMethods = new HashMap<>();
+ List<DexEncodedField> fields = new ArrayList<>();
+ readMethodTemplatesInto(codePrinter, generatedMethods::put, fields::add);
+ generateRawOutput(generatedMethods, fields, codePrinter, tempFile.toPath());
String result = formatRawOutput(tempFile.toPath());
tempFile.deleteOnExit();
return result;
}
- private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
+ private void readMethodTemplatesInto(
+ CfCodePrinter codePrinter,
+ BiConsumer<DexEncodedMethod, String> generatedMethods,
+ Consumer<DexEncodedField> generatedFields)
+ throws IOException {
InternalOptions options = new InternalOptions(factory, new Reporter());
options.testing.readInputStackMaps = true;
JarClassFileReader<DexProgramClass> reader =
new JarClassFileReader<>(
new JarApplicationReader(options),
clazz -> {
+ for (DexEncodedField field : clazz.fields()) {
+ DexEncodedField fieldToAddToClass = getField(field);
+ if (fieldToAddToClass != null) {
+ generatedFields.accept(fieldToAddToClass);
+ }
+ }
for (DexEncodedMethod method : clazz.allMethodsSorted()) {
- if (method.isInitializer()) {
+ if (!includeMethod(method)) {
continue;
}
String holderName = method.getHolderType().getName();
String methodName = method.getReference().name.toString();
+ if (methodName.equals("<init>")) {
+ methodName = "constructor_" + method.getProto().getArity();
+ }
String generatedMethodName = holderName + "_" + methodName;
CfCode code = getCode(holderName, methodName, method.getCode().asCfCode());
if (code != null) {
codePrinter.visitMethod(generatedMethodName, code);
+ generatedMethods.accept(method, generatedMethodName);
}
}
},
@@ -77,11 +128,222 @@
}
}
- private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
+ private String createType(DexType type) {
+ if (type.isVoidType()) {
+ return "factory.voidType";
+ }
+ if (type.isBooleanType()) {
+ return "factory.booleanType";
+ }
+ if (type.isIntType()) {
+ return "factory.intType";
+ }
+ if (type.isLongType()) {
+ return "factory.longType";
+ }
+ if (type.descriptor.toString().equals("Ljava/lang/Object;")) {
+ return "factory.objectType";
+ }
+ return "factory.createType(factory.createString(\"" + type.getDescriptor() + "\"))";
+ }
+
+ private String createField(DexEncodedField field) {
+ return "factory.createField(\n"
+ + "builder.getType(),\n"
+ + createType(field.getType())
+ + ",\n"
+ + "factory.createString(\""
+ + field.getName()
+ + "\"))\n";
+ }
+
+ private String buildSyntheticMethod(
+ DexEncodedMethod method, String codeGenerator, Set<String> requiredImports) {
+ String name =
+ method.isInstanceInitializer()
+ ? "constructor_" + method.getProto().getArity()
+ : method.getName().toString();
+ requiredImports.add("com.android.tools.r8.graph.DexEncodedMethod");
+ requiredImports.add("com.android.tools.r8.graph.MethodAccessFlags");
+ requiredImports.add("com.android.tools.r8.dex.Constants");
+ return "DexEncodedMethod.syntheticBuilder()\n"
+ + " .setMethod("
+ + name
+ + ")\n"
+ + " .setAccessFlags(\n"
+ + " MethodAccessFlags.fromSharedAccessFlags(\n"
+ + " Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, "
+ + (method.isInstanceInitializer())
+ + "))\n"
+ + " .setCode("
+ + codeGenerator
+ + "(factory, "
+ + name
+ + "))\n"
+ + " .disableAndroidApiLevelCheck()\n"
+ + " .build()";
+ }
+
+ private String generateCreateProto(DexProto proto) {
+ StringBuilder builder = new StringBuilder("factory.createProto(");
+ builder.append(createType(proto.returnType));
+ proto.getParameters().forEach(type -> builder.append(", ").append(createType(type)));
+ builder.append(")");
+ return builder.toString();
+ }
+
+ private String generateCreateMethod(DexEncodedMethod method) {
+ return "factory.createMethod(\n"
+ + "builder.getType(), "
+ + generateCreateProto(method.getProto())
+ + ", factory.createString(\""
+ + method.getName()
+ + "\"));";
+ }
+
+ private void generateDexMethodLocals(
+ Map<DexEncodedMethod, String> generatedMethods, PrintStream printer, Class<?> clazz) {
+ // Generate local variable for a DexMethod:
+ // DexMethod name = factory.createMethod(
+ // builder.getType(), factory.createProto(...), factory.createString(...));
+
+ generatedMethods.forEach(
+ (method, codeGenerator) -> {
+ if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
+ String name =
+ method.isInstanceInitializer()
+ ? "constructor_" + method.getProto().getArity()
+ : method.getName().toString();
+ printer.println("DexMethod " + name + " = " + generateCreateMethod(mapMethod(method)));
+ }
+ });
+ }
+
+ private void generateSyntheticMethodsList(
+ Map<DexEncodedMethod, String> generatedMethods,
+ Set<String> requiredImports,
+ PrintStream printer,
+ Class<?> clazz,
+ Predicate<DexEncodedMethod> filter) {
+ printer.println("ImmutableList.of(");
+ BooleanBox first = new BooleanBox(true);
+ generatedMethods.forEach(
+ (method, codeGenerator) -> {
+ if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())
+ && filter.test(method)) {
+ if (!first.get()) {
+ printer.println(",\n");
+ }
+ first.set(false);
+ printer.println(buildSyntheticMethod(method, codeGenerator, requiredImports));
+ }
+ });
+ printer.println(")");
+ }
+
+ private String buildSyntheticField(DexEncodedField field, Set<String> requiredImports) {
+ requiredImports.add("com.android.tools.r8.graph.DexEncodedField");
+ requiredImports.add("com.android.tools.r8.graph.FieldAccessFlags");
+ return " DexEncodedField.syntheticBuilder()\n"
+ + " .setField("
+ + createField(field)
+ + ")\n"
+ + " .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())\n"
+ + " .disableAndroidApiLevelCheck()\n"
+ + " .build()\n";
+ }
+
+ private void generateFieldList(
+ List<DexEncodedField> fields,
+ Set<String> requiredImports,
+ PrintStream printer,
+ Class<?> clazz) {
+ printer.println("ImmutableList.of(");
+ BooleanBox first = new BooleanBox(true);
+ for (DexEncodedField field : fields) {
+ if (field.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
+ if (!first.get()) {
+ printer.println(",\n");
+ }
+ first.set(false);
+ printer.println(buildSyntheticField(field, requiredImports));
+ }
+ }
+ printer.println(")");
+ }
+
+ private <K, V> Map<K, V> filterMapOnKey(Map<K, V> map, Predicate<K> predicate) {
+ return map.entrySet().stream()
+ .filter(entry -> predicate.test(entry.getKey()))
+ .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+ }
+
+ private void generateGenerateClasses(
+ Map<DexEncodedMethod, String> allMethods,
+ List<DexEncodedField> allFields,
+ Set<String> requiredImports,
+ PrintStream printer) {
+ for (Class<?> clazz : getClassesToGenerate()) {
+ String simpleName =
+ clazz.getSimpleName(); // "DesugarVarHandle"; //name.substring(name.lastIndexOf('.'));
+ List<DexEncodedField> classFields =
+ ListUtils.filter(
+ allFields,
+ field -> field.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
+ Map<DexEncodedMethod, String> classMethods =
+ filterMapOnKey(
+ allMethods,
+ method -> method.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
+ requiredImports.add("com.android.tools.r8.synthesis.SyntheticProgramClassBuilder");
+ printer.println(
+ "public static void generate"
+ + simpleName
+ + "Class(SyntheticProgramClassBuilder builder, DexItemFactory factory) {");
+ printer.println("builder.setInstanceFields(");
+ generateFieldList(classFields, requiredImports, printer, clazz);
+ printer.println(");");
+ generateDexMethodLocals(classMethods, printer, clazz);
+ if (clazz.getSuperclass() != Object.class) {
+ printer.println(
+ " builder.setSuperType("
+ + createType(factory.createType(Reference.classFromClass(clazz.getSuperclass())))
+ + ");");
+ }
+ printer.println("builder.setDirectMethods(");
+ generateSyntheticMethodsList(
+ classMethods, requiredImports, printer, clazz, DexEncodedMethod::isInstanceInitializer);
+ printer.println(");");
+ printer.println("builder.setVirtualMethods(");
+ generateSyntheticMethodsList(
+ allMethods, requiredImports, printer, clazz, method -> !method.isInstanceInitializer());
+ printer.println(");");
+ printer.println("}");
+ }
+ }
+
+ private void generateRawOutput(
+ Map<DexEncodedMethod, String> generatedMethods,
+ List<DexEncodedField> fields,
+ CfCodePrinter codePrinter,
+ Path tempFile)
+ throws IOException {
try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
printer.print(getHeaderString());
- printer.println("import com.android.tools.r8.graph.DexItemFactory;");
- codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
+
+ Set<String> imports = Sets.newHashSet();
+ imports.add("com.android.tools.r8.graph.DexItemFactory");
+ // TODO(b/260985726): Consider only calling generateGenerateClasses once.
+ // Generate classes into an unused PrintStream to collect imports.
+ generateGenerateClasses(
+ generatedMethods,
+ fields,
+ imports,
+ new PrintStream(new ByteArrayOutputStream(), true, StandardCharsets.UTF_8.name()));
+ imports.addAll(codePrinter.getImports());
+ ArrayList<String> sortedImports = new ArrayList<>(imports);
+ sortedImports.sort(String::compareTo);
+ sortedImports.forEach(i -> printer.println("import " + i + ";"));
+
printer.println("public final class " + getGeneratedClassName() + " {\n");
printer.println(
"public static void registerSynthesizedCodeReferences(DexItemFactory factory) {");
@@ -89,8 +351,12 @@
printer.println("factory.createSynthesizedType(\"" + type + "\");");
}
printer.println("}");
+ // TODO(b/260985726): Consider only calling generateGenerateClasses once.
+ // Generate classes ignoring the collected imports (they are already added to the file).
+ generateGenerateClasses(generatedMethods, fields, Sets.newHashSet(), printer);
codePrinter.getMethods().forEach(printer::println);
printer.println("}");
}
}
+
}
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
index 812eb63..b00d78b 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -10,7 +10,9 @@
import com.android.tools.r8.dex.code.DexConst4;
import com.android.tools.r8.dex.code.DexConstClass;
import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
import com.android.tools.r8.dex.code.DexInvokeVirtual;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
import com.android.tools.r8.dex.code.DexNewArray;
import com.android.tools.r8.dex.code.DexReturnVoid;
import com.android.tools.r8.graph.DexCode;
@@ -93,16 +95,29 @@
assertTrue(method.isPresent());
DexCode code = method.getMethod().getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof DexConst4);
- assertTrue(code.instructions[1] instanceof DexNewArray);
- assertTrue(code.instructions[2] instanceof DexConst4);
- assertTrue(code.instructions[3] instanceof DexConstClass);
- assertTrue(code.instructions[4] instanceof DexAputObject);
- assertTrue(code.instructions[5] instanceof DexConstClass);
- assertTrue(code.instructions[6] instanceof DexConstString);
- assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
- assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
- assertTrue(code.instructions[8] instanceof DexReturnVoid);
+
+ // Accept either array construction style (differs based on minSdkVersion).
+ if (code.instructions[1] instanceof DexFilledNewArray) {
+ assertTrue(code.instructions[0] instanceof DexConstClass);
+ assertTrue(code.instructions[1] instanceof DexFilledNewArray);
+ assertTrue(code.instructions[2] instanceof DexMoveResultObject);
+ assertTrue(code.instructions[3] instanceof DexConstClass);
+ assertTrue(code.instructions[4] instanceof DexConstString);
+ assertNotEquals("foo", code.instructions[4].asConstString().getString().toString());
+ assertTrue(code.instructions[5] instanceof DexInvokeVirtual);
+ assertTrue(code.instructions[6] instanceof DexReturnVoid);
+ } else {
+ assertTrue(code.instructions[0] instanceof DexConst4);
+ assertTrue(code.instructions[1] instanceof DexNewArray);
+ assertTrue(code.instructions[2] instanceof DexConst4);
+ assertTrue(code.instructions[3] instanceof DexConstClass);
+ assertTrue(code.instructions[4] instanceof DexAputObject);
+ assertTrue(code.instructions[5] instanceof DexConstClass);
+ assertTrue(code.instructions[6] instanceof DexConstString);
+ assertNotEquals("foo", code.instructions[6].asConstString().getString().toString());
+ assertTrue(code.instructions[7] instanceof DexInvokeVirtual);
+ assertTrue(code.instructions[8] instanceof DexReturnVoid);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java b/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java
new file mode 100644
index 0000000..018c618
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/AsmZeroLineEntryRegressionTest.java
@@ -0,0 +1,126 @@
+// 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 com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.IntBox;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Label;
+
+@RunWith(Parameterized.class)
+public class AsmZeroLineEntryRegressionTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultCfRuntime().build();
+ }
+
+ public AsmZeroLineEntryRegressionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ /**
+ * Reference test showing JVM printing of zero-line valued entries in the line number table.
+ *
+ * <p>JVM spec defines line values to be u2 (unsigned 2-byte) values without other restrictions.
+ */
+ @Test
+ public void testReference() throws Exception {
+ testForJvm()
+ .addProgramClassFileData(getClassWithZeroLineEntry())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .inspectOriginalStackTrace(st -> checkLineNumber(st, 0));
+ }
+
+ /**
+ * Regression test for ASM stripping out zero-line entries.
+ *
+ * <p>See b/260389461
+ */
+ @Test
+ public void testAsmIdentity() throws Exception {
+ testForJvm()
+ .addProgramClassFileData(
+ getClassAfterAsmIdentity(
+ getClassWithZeroLineEntry(), Reference.classFromClass(TestClass.class)))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ // If this becomes zero ASM has been updated to not assign line zero a special meaning.
+ .inspectOriginalStackTrace(st -> checkLineNumber(st, 1));
+ }
+
+ private void checkLineNumber(StackTrace st, int lineNumber) {
+ assertThat(
+ st,
+ isSame(
+ StackTrace.builder()
+ .add(
+ StackTraceLine.builder()
+ .setFileName(getClass().getSimpleName() + ".java")
+ .setClassName(TestClass.class.getTypeName())
+ .setMethodName("main")
+ .setLineNumber(lineNumber)
+ .build())
+ .build()));
+ }
+
+ private byte[] getClassAfterAsmIdentity(byte[] bytes, ClassReference clazz) {
+ return transformer(bytes, clazz)
+ // Add the identity line transform. If no method transformation is added then ASM will not
+ // interpret the line table and will retain the zero.
+ .addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ super.visitLineNumber(line, start);
+ }
+ })
+ .transform();
+ }
+
+ private byte[] getClassWithZeroLineEntry() throws IOException {
+ IntBox nextLine = new IntBox(1);
+ return transformer(TestClass.class)
+ .addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ if (getContext().getReference().getMethodName().equals("main")) {
+ int newLine = nextLine.get();
+ if (newLine > 0) {
+ nextLine.decrement(1);
+ }
+ super.visitLineNumber(newLine, start);
+ } else {
+ super.visitLineNumber(line, start);
+ }
+ }
+ })
+ .transform();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ throw new RuntimeException("BOO!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
index 053f4b3..455bfe4 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/CanonicalizeWithInline.java
@@ -3,24 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debuginfo;
-import static org.junit.Assert.assertNull;
import com.android.tools.r8.AssumeMayHaveSideEffects;
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.DexParser;
import com.android.tools.r8.dex.DexSection;
-import com.android.tools.r8.graph.DexDebugInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.io.IOException;
import java.nio.file.Path;
-import java.nio.file.Paths;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,7 +48,7 @@
Class<?> clazzA = ClassA.class;
Class<?> clazzB = ClassB.class;
- R8TestCompileResult result =
+ Path classesPath =
testForR8(Backend.DEX)
.setMinApi(AndroidApiLevel.B)
.addProgramClasses(clazzA, clazzB)
@@ -63,19 +57,11 @@
"-keep class ** { public void call(int); }")
.enableInliningAnnotations()
.enableSideEffectAnnotations()
- .compile();
- result.inspect(
- inspector -> {
- DexEncodedMethod method =
- inspector.clazz(ClassA.class).uniqueMethodWithOriginalName("call").getMethod();
- DexDebugInfo debugInfo = method.getCode().asDexCode().getDebugInfo();
- assertNull(debugInfo);
- });
- Path classesPath = temp.getRoot().toPath();
- result.app.write(classesPath, OutputMode.DexIndexed);
- int numberOfDebugInfos =
- getNumberOfDebugInfos(Paths.get(temp.getRoot().getCanonicalPath(), "classes.dex"));
- Assert.assertEquals(0, numberOfDebugInfos);
+ .compile()
+ .writeToDirectory();
+ int numberOfDebugInfos = getNumberOfDebugInfos(classesPath.resolve("classes.dex"));
+ // All methods have one parameter and use a single shared pc2pc item.
+ Assert.assertEquals(1, numberOfDebugInfos);
}
// Two classes which has debug info that looks exactly the same, except for SetInlineFrame.
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java
index c577259..84db0e5 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoInlineRemoveTest.java
@@ -51,6 +51,11 @@
.map(StackTrace::extractFromJvm);
}
+ private boolean compileApiHasPcLineSupport() {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
+ }
+
@Test
public void testDefaultSourceFile() throws Exception {
testForR8(parameters.getBackend())
@@ -70,7 +75,7 @@
assertThat(mainSubject.uniqueMethodWithOriginalName("inlinee"), not(isPresent()));
assertThat(
mainSubject.uniqueMethodWithOriginalName("shouldRemoveLineNumberForInline"),
- notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+ notIf(hasLineNumberTable(), compileApiHasPcLineSupport()));
});
}
@@ -94,7 +99,7 @@
assertThat(mainSubject.uniqueMethodWithOriginalName("inlinee"), not(isPresent()));
assertThat(
mainSubject.uniqueMethodWithOriginalName("shouldRemoveLineNumberForInline"),
- notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+ notIf(hasLineNumberTable(), compileApiHasPcLineSupport()));
});
}
@@ -118,7 +123,7 @@
assertThat(mainSubject.uniqueMethodWithOriginalName("inlinee"), not(isPresent()));
assertThat(
mainSubject.uniqueMethodWithOriginalName("shouldRemoveLineNumberForInline"),
- notIf(hasLineNumberTable(), parameters.isDexRuntime()));
+ notIf(hasLineNumberTable(), compileApiHasPcLineSupport()));
});
}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java
index 542cfb6..e1075c6 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoMultipleCallsRemoveTest.java
@@ -5,9 +5,6 @@
package com.android.tools.r8.debuginfo;
import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
-import static com.android.tools.r8.utils.codeinspector.Matchers.hasLineNumberTable;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -17,7 +14,6 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfRuntime;
import com.android.tools.r8.naming.retrace.StackTrace;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,20 +59,7 @@
.enableInliningAnnotations()
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatThrows(NullPointerException.class)
- .inspectStackTrace(
- (stackTrace, inspector) -> {
- assertThat(stackTrace, isSame(expectedStackTrace));
- assertThat(inspector.clazz(Builder.class), isPresent());
- ClassSubject mainSubject = inspector.clazz(Main.class);
- assertThat(mainSubject, isPresent());
- assertThat(
- mainSubject.uniqueMethodWithOriginalName(
- "shouldRemoveLineNumberForMultipleInvokes"),
- notIf(hasLineNumberTable(), parameters.isDexRuntime()));
- assertThat(
- mainSubject.uniqueMethodWithOriginalName("main"),
- notIf(hasLineNumberTable(), parameters.isDexRuntime()));
- });
+ .inspectStackTrace(stackTrace -> assertThat(stackTrace, isSame(expectedStackTrace)));
}
@NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
index 8076acf..46b9e22 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SingleLineInfoRemoveTest.java
@@ -98,7 +98,9 @@
}
private boolean canSingleLineDebugInfoBeDiscarded() {
- return parameters.isDexRuntime() && !customSourceFile;
+ return parameters.isDexRuntime()
+ && !customSourceFile
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
}
public static class Main {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ZeroLineEntryTest.java b/src/test/java/com/android/tools/r8/debuginfo/ZeroLineEntryTest.java
new file mode 100644
index 0000000..4b7259f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/ZeroLineEntryTest.java
@@ -0,0 +1,70 @@
+// 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 com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ZeroLineEntryTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public ZeroLineEntryTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ SmaliBuilder smali = new SmaliBuilder();
+ smali.addClass("Test");
+ smali.setSourceFile("Test.java");
+ smali.addMainMethod(
+ 0,
+ ".line 42",
+ "invoke-static {}, Ljava/lang/System;->nanoTime()J",
+ ".line 0",
+ "new-instance v0, Ljava/lang/RuntimeException;",
+ "invoke-direct {v0}, Ljava/lang/RuntimeException;-><init>()V",
+ ".line 123",
+ "throw v0");
+
+ testForRuntime(parameters)
+ .addProgramDexFileData(smali.compile())
+ .run(parameters.getRuntime(), "Test")
+ .assertFailureWithErrorThatThrows(RuntimeException.class)
+ .inspectStackTrace(this::checkStackTrace);
+ }
+
+ private void checkStackTrace(StackTrace stacktrace) {
+ assertThat(
+ stacktrace,
+ isSame(
+ StackTrace.builder()
+ .add(
+ StackTraceLine.builder()
+ .setLineNumber(0)
+ .setMethodName("main")
+ .setClassName("Test")
+ .setFileName("Test.java")
+ .build())
+ .build()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
index 398ecb4..c036aaf 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/pc2pc/DifferentParameterCountSingleLineCodeTestRunner.java
@@ -5,7 +5,6 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
@@ -58,15 +57,13 @@
// For a custom source file, all debug info must be present.
assertEquals("X", line.fileName);
assertTrue("Expected line number in: " + line, line.hasLineNumber());
- } else if (vmHasPcSupport()) {
- // Single line debug info is stripped. If running with PC support the PC is
- // printed.
+ } else if (compileApiHasPcSupport()) {
assertEquals("Unknown Source", line.fileName);
assertTrue("Expected PC in: " + line, line.hasLineNumber());
} else {
- // Otherwise, just the bare source file is printed.
+ // Otherwise, the bare source file is printed and a line.
assertEquals("SourceFile", line.fileName);
- assertFalse("Expected no line number in: " + line, line.hasLineNumber());
+ assertTrue("Expected line number in: " + line, line.hasLineNumber());
}
}
assertEquals("Expected 4 stack frames in:\n" + s, 4, s.getStackTraceLines().size());
@@ -84,11 +81,8 @@
.build())));
}
- private boolean vmHasPcSupport() {
- return parameters
- .asDexRuntime()
- .maxSupportedApiLevel()
- .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
+ private boolean compileApiHasPcSupport() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport());
}
private StackTraceLine makeLine(String methodName, int lineNumber) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
index 4602235..a54633b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ExtractWrapperTypesTest.java
@@ -123,7 +123,13 @@
// TODO(b/238179854): Investigate how to fix these.
private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION_PATH =
- ImmutableSet.of("java.lang.Iterable java.nio.file.FileSystem.getFileStores()");
+ ImmutableSet.of(
+ "java.lang.Iterable java.nio.file.FileSystem.getFileStores()",
+ // The Acl seems to be unusable on Android anyway.
+ "java.util.Set java.nio.file.attribute.AclEntry.permissions()",
+ "java.util.Set java.nio.file.attribute.AclEntry.flags()",
+ "java.util.List java.nio.file.attribute.AclFileAttributeView.getAcl()",
+ "void java.nio.file.attribute.AclFileAttributeView.setAcl(java.util.List)");
// TODO(b/238179854): Investigate how to fix these.
private static final Set<String> MISSING_GENERIC_TYPE_CONVERSION_FLOW =
@@ -233,10 +239,13 @@
specification.getWrappers().keySet().stream()
.map(DexType::toString)
.collect(Collectors.toSet());
- Set<String> customConversionsInSpec =
+ Set<String> customConversionsOnly =
specification.getCustomConversions().keySet().stream()
.map(DexType::toString)
.collect(Collectors.toSet());
+ // Some types are present both as custom conversions and wrappers, so that the custom conversion
+ // can catch some specific cases on top of the wrapper. We are not interested in those.
+ customConversionsOnly.removeAll(wrappersInSpec);
Set<String> maintainTypeInSet =
specification.getMaintainType().stream().map(DexType::toString).collect(Collectors.toSet());
Map<String, boolean[]> genericConversionsInSpec = new HashMap<>();
@@ -251,9 +260,6 @@
genericConversionsInSpec.put(method.toString(), indexes);
});
- assertEquals(
- Collections.emptySet(), Sets.intersection(wrappersInSpec, customConversionsInSpec));
-
CodeInspector nonDesugaredJar = new CodeInspector(ToolHelper.getAndroidJar(targetApi));
Set<DexEncodedMethod> genericDependencies = new HashSet<>();
Map<ClassReference, Set<MethodReference>> directWrappers =
@@ -261,7 +267,7 @@
desugaredApiJar,
preDesugarTypes,
nonDesugaredJar,
- customConversionsInSpec,
+ customConversionsOnly,
maintainTypeInSet,
genericConversionsInSpec,
genericDependencies);
@@ -270,7 +276,7 @@
directWrappers,
preDesugarTypes,
nonDesugaredJar,
- customConversionsInSpec,
+ customConversionsOnly,
maintainTypeInSet,
specification.getWrappers(),
genericConversionsInSpec,
@@ -301,7 +307,10 @@
// java.util.stream.Collector$Characteristics is required for api generic type conversion
// on JDK8, but that is not supported on legacy specification used for JDK8 and on old
// R8 compiler versions.
- int expectedMissingWrappers = libraryDesugaringSpecification == JDK8 ? 1 : 0;
+ int expectedMissingWrappers =
+ libraryDesugaringSpecification == JDK8
+ ? 1
+ : (libraryDesugaringSpecification == JDK11_PATH ? 4 : 0);
{
Set<String> missingWrappers = getMissingWrappers(indirectWrappers, wrappersInSpec);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java
new file mode 100644
index 0000000..af5df8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesCreateTest.java
@@ -0,0 +1,220 @@
+// 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.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FilesCreateTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "ind3f class java.nio.file.NoSuchFileException :: dir",
+ "ind4f class java.nio.file.NoSuchFileException :: dir",
+ "ind5f class java.nio.file.NoSuchFileException :: f.txt",
+ "ind6f class java.nio.file.NoSuchFileException :: f.txt",
+ "true",
+ "true",
+ "notExisting1 class java.nio.file.NoSuchFileException :: notExisting1.txt",
+ "false",
+ "false",
+ "readAttributes class java.nio.file.NoSuchFileException :: f1.txt",
+ "readAttributes class java.nio.file.NoSuchFileException :: f1.txt");
+ private static final String EXPECTED_RESULT_DESUGARING =
+ StringUtils.lines(
+ "ind3f class java.nio.file.NoSuchFileException :: dir",
+ "ind4f class java.nio.file.NoSuchFileException :: dir",
+ "ind5f class java.io.FileNotFoundException :: f.txt: open failed: ENOENT (No such file or"
+ + " directory)",
+ "ind6f class java.io.FileNotFoundException :: f.txt: open failed: ENOENT (No such file or"
+ + " directory)",
+ "true",
+ "true",
+ "notExisting1 class java.io.IOException :: notExisting1.txt before deletion.",
+ "false",
+ "true",
+ "1970-01-01T00:00:00Z",
+ "1970-01-01T00:00:00Z");
+ private static final String EXPECTED_FILES =
+ StringUtils.lines(
+ "ind2s/dir", "ind2s", "ind1s/dir", "ind1s", "f4.txt", "f3.txt", "dir2s", "dir1s");
+
+ private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+ private final CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ // Skip Android 4.4.4 due to missing libjavacrypto.
+ getTestParameters()
+ .withCfRuntime(CfVm.JDK11)
+ .withDexRuntime(Version.V4_0_4)
+ .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+ .withAllApiLevels()
+ .build(),
+ ImmutableList.of(JDK11_PATH),
+ DEFAULT_SPECIFICATIONS);
+ }
+
+ public FilesCreateTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ @Test
+ public void test() throws Throwable {
+ if (parameters.isCfRuntime()) {
+ // Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8
+ // behaves differently on this test.
+ Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows());
+ testForJvm()
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpectedResult());
+ return;
+ }
+ Assume.assumeFalse(
+ "The command mkdir fails on Android 7.0, we need to investigate if this is an emulator"
+ + "issue or a real issue.",
+ parameters.getDexRuntimeVersion().equals(Version.V7_0_0));
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpectedResult());
+ }
+
+ private String getExpectedResult() {
+ if (parameters.isCfRuntime()) {
+ return EXPECTED_RESULT + EXPECTED_FILES;
+ }
+ return (libraryDesugaringSpecification.usesPlatformFileSystem(parameters)
+ ? EXPECTED_RESULT
+ : EXPECTED_RESULT_DESUGARING)
+ + EXPECTED_FILES;
+ }
+
+ public static class TestClass {
+
+ private static void printError(Throwable t, String prefix) {
+ String[] split =
+ t.getMessage() == null ? new String[] {"no-message"} : t.getMessage().split("/");
+ System.out.println(prefix + " " + t.getClass() + " :: " + split[split.length - 1]);
+ }
+
+ public static void main(String[] args) throws Throwable {
+ Path root = Files.createTempDirectory("tmp_test");
+ Files.createDirectories(root.resolve("ind1s/dir"));
+ Files.createDirectories(root.resolve("ind2s/dir"), getFileAttribute());
+ try {
+ Files.createDirectory(root.resolve("ind3f/dir"));
+ } catch (Throwable t) {
+ printError(t, "ind3f");
+ }
+ try {
+ Files.createDirectory(root.resolve("ind4f/dir"), getFileAttribute());
+ } catch (Throwable t) {
+ printError(t, "ind4f");
+ }
+ Files.createDirectory(root.resolve("dir1s"));
+ Files.createDirectory(root.resolve("dir2s"), getFileAttribute());
+ try {
+ Files.createFile(root.resolve("ind5f/f.txt"));
+ } catch (Throwable t) {
+ printError(t, "ind5f");
+ }
+ try {
+ Files.createFile(root.resolve("ind6f/f.txt"), getFileAttribute());
+ } catch (Throwable t) {
+ printError(t, "ind6f");
+ }
+ Files.createFile(root.resolve("f1.txt"));
+ Files.createFile(root.resolve("f2.txt"), getFileAttribute());
+
+ System.out.println(Files.exists(root.resolve("f1.txt")));
+ System.out.println(Files.exists(root.resolve("f1.txt"), LinkOption.NOFOLLOW_LINKS));
+
+ Files.delete(root.resolve("f1.txt"));
+ try {
+ Files.delete(root.resolve("notExisting1.txt"));
+ } catch (Throwable t) {
+ printError(t, "notExisting1");
+ }
+ Files.deleteIfExists(root.resolve("f2.txt"));
+ Files.deleteIfExists(root.resolve("notExisting2.txt"));
+
+ System.out.println(Files.exists(root.resolve("f1.txt")));
+ System.out.println(Files.exists(root.resolve("f1.txt"), LinkOption.NOFOLLOW_LINKS));
+ try {
+ System.out.println(
+ Files.readAttributes(root.resolve("f1.txt"), BasicFileAttributes.class)
+ .lastAccessTime());
+ } catch (Throwable t) {
+ printError(t, "readAttributes");
+ }
+ try {
+ System.out.println(
+ Files.readAttributes(
+ root.resolve("f1.txt"), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)
+ .lastAccessTime());
+ } catch (Throwable t) {
+ printError(t, "readAttributes");
+ }
+
+ // Recreate for the final print.
+ Files.createFile(root.resolve("f3.txt"));
+ Files.createFile(root.resolve("f4.txt"), getFileAttribute());
+
+ Files.walk(root)
+ .sorted(Comparator.reverseOrder())
+ .map(
+ f -> {
+ if (f != root) {
+ System.out.println(f.subpath(2, f.getNameCount()));
+ }
+ return f.toFile();
+ })
+ .forEach(File::delete);
+ }
+
+ public static FileAttribute<Set<PosixFilePermission>> getFileAttribute() {
+ return PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr-xr-x"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveCopyTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveCopyTest.java
new file mode 100644
index 0000000..e6eeff2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveCopyTest.java
@@ -0,0 +1,259 @@
+// 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.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY_FROM_INPUT_STREAM;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.COPY_TO_OUTPUT_STREAM;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.CopyOrMove.MOVE;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.NewFile.EXISTING;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdk11.FilesMoveCopyTest.TestClass.NewFile.NEW;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.CopyOption;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests Files#copy and Files#move methods. Known limitations/details below 26: - ATOMIC MOVE is a
+ * no-op, moves are effectively file renaming which seem to be atomic on Android. - COPY_ATTRIBUTES
+ * is a no-op, except if unsupported in which case it throws appropriately, now it seems only basic
+ * file attributes are supported below 26 anyway and such attributes are not meaningfully copied
+ * around anyway. Creation/last modification/last access times are set to the time of the move/copy,
+ * and the other ones such as "isDirectory" cannot be easily abused.
+ */
+@RunWith(Parameterized.class)
+public class FilesMoveCopyTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT_FORMAT =
+ StringUtils.lines(
+ "Testing COPY_TO_OUTPUT_STREAM EXISTING NONE",
+ "COPY_TO_OUTPUT_STREAM_NONE",
+ "Testing MOVE NEW NONE",
+ "MOVE_NONE",
+ "Testing COPY NEW NONE",
+ "COPY_NONE",
+ "Testing COPY_FROM_INPUT_STREAM NEW NONE",
+ "COPY_FROM_INPUT_STREAM_NONE",
+ "Testing MOVE EXISTING NONE",
+ "Failure: class java.nio.file.FileAlreadyExistsException :: dest",
+ "Testing COPY EXISTING NONE",
+ "Failure: class java.nio.file.FileAlreadyExistsException :: dest",
+ "Testing COPY_FROM_INPUT_STREAM EXISTING NONE",
+ "%s",
+ "Testing MOVE NEW ATOMIC_MOVE",
+ "MOVE_ATOMIC_MOVE",
+ "Testing COPY NEW ATOMIC_MOVE",
+ "Failure: class java.lang.UnsupportedOperationException :: Unsupported copy option",
+ "Testing COPY_FROM_INPUT_STREAM NEW ATOMIC_MOVE",
+ "Failure: class java.lang.UnsupportedOperationException :: ATOMIC_MOVE not supported",
+ "Testing MOVE NEW COPY_ATTRIBUTES",
+ "Failure: class java.lang.UnsupportedOperationException :: Unsupported copy option",
+ "Testing COPY NEW COPY_ATTRIBUTES",
+ "COPY_COPY_ATTRIBUTES",
+ "Testing COPY_FROM_INPUT_STREAM NEW COPY_ATTRIBUTES",
+ "Failure: class java.lang.UnsupportedOperationException :: COPY_ATTRIBUTES not supported",
+ "Testing MOVE NEW REPLACE_EXISTING",
+ "MOVE_REPLACE_EXISTING",
+ "Testing COPY NEW REPLACE_EXISTING",
+ "COPY_REPLACE_EXISTING",
+ "Testing COPY_FROM_INPUT_STREAM NEW REPLACE_EXISTING",
+ "COPY_FROM_INPUT_STREAM_REPLACE_EXISTING",
+ "Testing MOVE EXISTING REPLACE_EXISTING",
+ "MOVE_REPLACE_EXISTING",
+ "Testing COPY EXISTING REPLACE_EXISTING",
+ "COPY_REPLACE_EXISTING",
+ "Testing COPY_FROM_INPUT_STREAM EXISTING REPLACE_EXISTING",
+ "COPY_FROM_INPUT_STREAM_REPLACE_EXISTING",
+ "Testing MOVE NEW NOFOLLOW_LINKS",
+ "MOVE_NOFOLLOW_LINKS",
+ "Testing COPY NEW NOFOLLOW_LINKS",
+ "COPY_NOFOLLOW_LINKS",
+ "Testing COPY_FROM_INPUT_STREAM NEW NOFOLLOW_LINKS",
+ "Failure: class java.lang.UnsupportedOperationException :: NOFOLLOW_LINKS not supported");
+
+ private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+ private final CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ // Skip Android 4.4.4 due to missing libjavacrypto.
+ getTestParameters()
+ .withCfRuntime(CfVm.JDK11)
+ .withDexRuntime(Version.V4_0_4)
+ .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+ .withAllApiLevels()
+ .build(),
+ ImmutableList.of(JDK11_PATH),
+ DEFAULT_SPECIFICATIONS);
+ }
+
+ public FilesMoveCopyTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ private String getExpectedResult() {
+ // TODO(b/260208125): Fix invalid behavior.
+ // When copying from the input stream into an existing file and no options,
+ // the desugared version succeeds by deleting the file while the regular version fails
+ // with FileAlreadyExistsException exception.
+ String result =
+ (parameters.isDexRuntime()
+ && !libraryDesugaringSpecification.usesPlatformFileSystem(parameters))
+ ? "COPY_FROM_INPUT_STREAM_NONE"
+ : "Failure: class java.nio.file.FileAlreadyExistsException :: dest";
+ return String.format(EXPECTED_RESULT_FORMAT, result);
+ }
+
+ @Test
+ public void test() throws Throwable {
+ if (parameters.isCfRuntime()) {
+ // Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8
+ // behaves differently on this test.
+ Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows());
+ testForJvm()
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpectedResult());
+ return;
+ }
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(getExpectedResult());
+ }
+
+ public static class TestClass {
+
+ enum CopyOrMove {
+ COPY,
+ COPY_FROM_INPUT_STREAM,
+ COPY_TO_OUTPUT_STREAM,
+ MOVE
+ }
+
+ enum NewFile {
+ EXISTING,
+ NEW
+ }
+
+ public static void main(String[] args) throws Throwable {
+ test(COPY_TO_OUTPUT_STREAM, EXISTING, null);
+ for (CopyOption copyOption : allOptions()) {
+ test(MOVE, NEW, copyOption);
+ test(COPY, NEW, copyOption);
+ test(COPY_FROM_INPUT_STREAM, NEW, copyOption);
+ if (copyOption == null || copyOption == StandardCopyOption.REPLACE_EXISTING) {
+ test(MOVE, EXISTING, copyOption);
+ test(COPY, EXISTING, copyOption);
+ test(COPY_FROM_INPUT_STREAM, EXISTING, copyOption);
+ }
+ }
+ }
+
+ private static CopyOption[] allOptions() {
+ return new CopyOption[] {
+ null,
+ StandardCopyOption.ATOMIC_MOVE,
+ StandardCopyOption.COPY_ATTRIBUTES,
+ StandardCopyOption.REPLACE_EXISTING,
+ LinkOption.NOFOLLOW_LINKS
+ };
+ }
+
+ private static void clearTmp(Path src, Path dest) throws IOException {
+ if (Files.exists(src)) {
+ Files.delete(src);
+ }
+ if (Files.exists(dest)) {
+ Files.delete(dest);
+ }
+ }
+
+ private static void test(CopyOrMove copyOrMove, NewFile newFile, CopyOption copyOption)
+ throws IOException {
+ String copyOptionString = copyOption == null ? "NONE" : copyOption.toString();
+ CopyOption[] opts = copyOption == null ? new CopyOption[0] : new CopyOption[] {copyOption};
+ System.out.println("Testing " + copyOrMove + " " + newFile + " " + copyOptionString);
+ Path src = Files.createTempFile("src_", ".txt");
+ Path dest =
+ newFile == NEW
+ ? src.getParent().resolve("dest_" + System.currentTimeMillis() + ".txt")
+ : Files.createTempFile("dest_", ".txt");
+ String toWrite = copyOrMove + "_" + copyOptionString;
+ Files.write(src, toWrite.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);
+ try {
+ switch (copyOrMove) {
+ case COPY:
+ Files.copy(src, dest, opts);
+ break;
+ case MOVE:
+ Files.move(src, dest, opts);
+ break;
+ case COPY_FROM_INPUT_STREAM:
+ FileInputStream in = new FileInputStream(src.toFile());
+ Files.copy(in, dest, opts);
+ in.close();
+ break;
+ case COPY_TO_OUTPUT_STREAM:
+ if (opts.length != 0) {
+ throw new UnsupportedOperationException("Api does not encode options");
+ }
+ FileOutputStream out = new FileOutputStream(dest.toFile());
+ Files.copy(src, out);
+ out.close();
+ break;
+ }
+ System.out.println(Files.readAllLines(dest).get(0));
+ } catch (Throwable t) {
+ String subMessage;
+ if (t instanceof FileAlreadyExistsException && t.getMessage() != null) {
+ // The message is the file path, however we cannot rely on temp path, so we check
+ // only against the known part of the path.
+ String[] split = t.getMessage().split("/");
+ subMessage = split[split.length - 1].substring(0, 4);
+ } else {
+ subMessage = t.getMessage();
+ }
+ System.out.println("Failure: " + t.getClass() + " :: " + subMessage);
+ }
+ clearTmp(src, dest);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveTest.java
deleted file mode 100644
index 97feb33..0000000
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesMoveTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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.jdk11;
-
-import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
-import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
-
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
-import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class FilesMoveTest extends DesugaredLibraryTestBase {
-
- private static final String EXPECTED_RESULT = StringUtils.lines("Hello");
-
- private final TestParameters parameters;
- private final LibraryDesugaringSpecification libraryDesugaringSpecification;
- private final CompilationSpecification compilationSpecification;
-
- @Parameters(name = "{0}, spec: {1}, {2}")
- public static List<Object[]> data() {
- return buildParameters(
- // Skip Android 4.4.4 due to missing libjavacrypto.
- getTestParameters()
- .withDexRuntime(Version.V4_0_4)
- .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
- .withAllApiLevels()
- .build(),
- ImmutableList.of(JDK11_PATH),
- DEFAULT_SPECIFICATIONS);
- }
-
- public FilesMoveTest(
- TestParameters parameters,
- LibraryDesugaringSpecification libraryDesugaringSpecification,
- CompilationSpecification compilationSpecification) {
- this.parameters = parameters;
- this.libraryDesugaringSpecification = libraryDesugaringSpecification;
- this.compilationSpecification = compilationSpecification;
- }
-
- @Test
- public void test() throws Throwable {
- testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
- .addInnerClasses(getClass())
- .addKeepMainRule(TestClass.class)
- .compile()
- .withArt6Plus64BitsLib()
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED_RESULT);
- }
-
- public static class TestClass {
-
- public static void main(String[] args) throws Throwable {
- Path src = Files.createTempFile("src", ".txt");
- Path dest = src.getParent().resolve("dest");
- Files.write(src, "Hello".getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);
- Files.move(src, dest, StandardCopyOption.ATOMIC_MOVE);
- System.out.println(Files.readAllLines(dest).get(0));
- }
- }
-}
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 5ae7b6c..27f42f0 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
@@ -6,6 +6,8 @@
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -25,12 +27,20 @@
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.DosFileAttributeView;
+import java.nio.file.attribute.DosFileAttributes;
+import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -45,47 +55,51 @@
@RunWith(Parameterized.class)
public class FilesTest extends DesugaredLibraryTestBase {
- private static final String END_EXPECTED_RESULT =
- StringUtils.lines("tmp", "/", "true", "This", "is", "fun!");
- private static final String EXPECTED_RESULT_DESUGARING_FILE_SYSTEM =
+ private static final String EXPECTED_RESULT_FORMAT =
StringUtils.lines(
- "bytes written: 11",
- "String written: Hello World",
- "bytes read: 11",
- "String read: Hello World",
- "bytes read: 11",
- "String read: Hello World",
- "null",
- "true",
- "unsupported",
- "j$.nio.file.attribute")
- + END_EXPECTED_RESULT;
- private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING =
- StringUtils.lines(
- "bytes written: 11",
- "String written: Hello World",
- "bytes read: 11",
- "String read: Hello World",
- "bytes read: 11",
- "String read: Hello World",
- "true",
- "true",
- "true",
- "j$.nio.file.attribute")
- + END_EXPECTED_RESULT;
- private static final String EXPECTED_RESULT_PLATFORM_FILE_SYSTEM =
- StringUtils.lines(
- "bytes written: 11",
- "String written: Hello World",
- "bytes read: 11",
- "String read: Hello World",
- "bytes read: 11",
- "String read: Hello World",
- "true",
- "true",
- "true",
- "java.nio.file.attribute")
- + END_EXPECTED_RESULT;
+ "bytes written: 11",
+ "String written: Hello World",
+ "bytes read: 11",
+ "String read: Hello World",
+ "bytes read: 11",
+ "String read: Hello World",
+ "true",
+ "%s",
+ "null",
+ "null",
+ "%s",
+ "null",
+ "true",
+ "%s",
+ "unsupported",
+ "tmp",
+ "/",
+ "true",
+ "tmpFile",
+ "This",
+ "is",
+ "fun!",
+ "%s",
+ "%s",
+ "%s",
+ "%s");
+ private static final List<String> EXPECTED_RESULT_POSIX =
+ ImmutableList.of(
+ "true",
+ "true",
+ "true",
+ "Succeeded with POSIX RO:false",
+ "Successfully set RO with POSIX",
+ "Succeeded with POSIX RO:true");
+ private static final List<String> EXPECTED_RESULT_DESUGARING_NON_POSIX =
+ ImmutableList.of(
+ "null",
+ "null",
+ "unsupported",
+ "Fail to understand if the file is read-only: class"
+ + " java.lang.UnsupportedOperationException",
+ "Fail to set file as read-only: class java.lang.UnsupportedOperationException",
+ "NotSet");
private final TestParameters parameters;
private final LibraryDesugaringSpecification libraryDesugaringSpecification;
@@ -113,13 +127,21 @@
this.compilationSpecification = compilationSpecification;
}
+ private static String computeExpectedResult(boolean supportPosix, boolean j$nioClasses) {
+ List<String> strings =
+ new ArrayList<>(
+ supportPosix ? EXPECTED_RESULT_POSIX : EXPECTED_RESULT_DESUGARING_NON_POSIX);
+ strings.add(j$nioClasses ? "j$.nio.file.attribute" : "java.nio.file.attribute");
+ return String.format(EXPECTED_RESULT_FORMAT, strings.toArray());
+ }
+
private String getExpectedResult() {
if (libraryDesugaringSpecification.usesPlatformFileSystem(parameters)) {
return libraryDesugaringSpecification.hasNioFileDesugaring(parameters)
- ? EXPECTED_RESULT_PLATFORM_FILE_SYSTEM_DESUGARING
- : EXPECTED_RESULT_PLATFORM_FILE_SYSTEM;
+ ? computeExpectedResult(true, true)
+ : computeExpectedResult(true, false);
}
- return EXPECTED_RESULT_DESUGARING_FILE_SYSTEM;
+ return computeExpectedResult(false, true);
}
@Test
@@ -139,11 +161,81 @@
Path path = Files.createTempFile("example", ".txt");
readWriteThroughFilesAPI(path);
readThroughFileChannelAPI(path);
+ attributeViewAccess(path);
attributeAccess(path);
Files.setAttribute(path, "basic:lastModifiedTime", FileTime.from(Instant.EPOCH));
- fspMethodsWithGeneric(path);
pathGeneric();
lines(path);
+ readOnlyTest(path);
+ fspMethodsWithGeneric(path);
+ }
+
+ private static void readOnlyTest(Path path) {
+ isReadOnly(path);
+ if (setReadOnly(path)) {
+ isReadOnly(path);
+ } else {
+ System.out.println("NotSet");
+ }
+ }
+
+ private static boolean isReadOnly(Path path) {
+ try {
+ // DOS attempt.
+ try {
+ DosFileAttributeView dosFileAttributeView =
+ Files.getFileAttributeView(path, DosFileAttributeView.class);
+ if (dosFileAttributeView != null && dosFileAttributeView.readAttributes() != null) {
+ boolean readOnly = dosFileAttributeView.readAttributes().isReadOnly();
+ System.out.println("Succeeded with DOS RO:" + readOnly);
+ return readOnly;
+ }
+ } catch (IOException ignored) {
+ }
+ // Posix attempt.
+ Set<PosixFilePermission> posixFilePermissions = Files.getPosixFilePermissions(path);
+ boolean readOnly =
+ posixFilePermissions.contains(OWNER_READ)
+ && !posixFilePermissions.contains(OWNER_WRITE);
+ System.out.println("Succeeded with POSIX RO:" + readOnly);
+ return readOnly;
+
+ } catch (Throwable t) {
+ System.out.println("Fail to understand if the file is read-only: " + t.getClass());
+ return false;
+ }
+ }
+
+ /** Common pattern to set a file as read-only: Try on Dos, on failure, retry on Posix. */
+ private static boolean setReadOnly(Path path) {
+ try {
+
+ // DOS attempt.
+ try {
+ DosFileAttributeView dosFileAttributeView =
+ Files.getFileAttributeView(path, DosFileAttributeView.class);
+ if (dosFileAttributeView != null) {
+ dosFileAttributeView.setReadOnly(true);
+ System.out.println("Successfully set RO with DOS");
+ return true;
+ }
+ } catch (IOException ignored) {
+ }
+
+ // Posix attempt.
+ Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
+ List<PosixFilePermission> readPermissions = Arrays.asList(OWNER_READ);
+ List<PosixFilePermission> writePermissions = Arrays.asList(PosixFilePermission.OWNER_WRITE);
+ permissions.addAll(readPermissions);
+ permissions.removeAll(writePermissions);
+ Files.setPosixFilePermissions(path, permissions);
+ System.out.println("Successfully set RO with POSIX");
+ return true;
+
+ } catch (Throwable t) {
+ System.out.println("Fail to set file as read-only: " + t.getClass());
+ return false;
+ }
}
private static void pathGeneric() throws IOException {
@@ -154,7 +246,9 @@
Iterable<Path> rootDirectories = tmpFile.getFileSystem().getRootDirectories();
System.out.println(rootDirectories.iterator().next());
DirectoryStream<Path> paths = Files.newDirectoryStream(tmpDict);
- System.out.println(paths.iterator().hasNext());
+ Iterator<Path> theIterator = paths.iterator();
+ System.out.println(theIterator.hasNext());
+ System.out.println(theIterator.next().getFileName());
}
private static void fspMethodsWithGeneric(Path path) throws IOException {
@@ -162,15 +256,71 @@
System.out.println(mapping.values().iterator().next().getClass().getPackage().getName());
}
- private static void attributeAccess(Path path) throws IOException {
- PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
- if (view != null) {
- System.out.println(
- view.readAttributes().permissions().contains(PosixFilePermission.OWNER_READ));
+ private static void attributeViewAccess(Path path) throws IOException {
+ BasicFileAttributeView basicView =
+ Files.getFileAttributeView(path, BasicFileAttributeView.class);
+ if (basicView != null) {
+ System.out.println(basicView.readAttributes().isRegularFile());
} else {
System.out.println("null");
}
+ PosixFileAttributeView posixView =
+ Files.getFileAttributeView(path, PosixFileAttributeView.class);
+ if (posixView != null) {
+ System.out.println(posixView.readAttributes().permissions().contains(OWNER_READ));
+ } else {
+ System.out.println("null");
+ }
+
+ try {
+ DosFileAttributeView dosView = Files.getFileAttributeView(path, DosFileAttributeView.class);
+ if (dosView != null) {
+ System.out.println(dosView.readAttributes().isReadOnly());
+ } else {
+ System.out.println("null");
+ }
+ } catch (UnsupportedOperationException e) {
+ System.out.println("unsupported");
+ }
+
+ try {
+ AclFileAttributeView aclView = Files.getFileAttributeView(path, AclFileAttributeView.class);
+ if (aclView != null) {
+ System.out.println(aclView.getAcl().isEmpty());
+ } else {
+ System.out.println("null");
+ }
+ } catch (UnsupportedOperationException e) {
+ System.out.println("unsupported");
+ }
+
+ try {
+ FileOwnerAttributeView foView =
+ Files.getFileAttributeView(path, FileOwnerAttributeView.class);
+ if (foView != null) {
+ System.out.println(foView.getOwner() != null);
+ } else {
+ System.out.println("null");
+ }
+ } catch (UnsupportedOperationException e) {
+ System.out.println("unsupported");
+ }
+
+ try {
+ UserDefinedFileAttributeView udView =
+ Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+ if (udView != null) {
+ System.out.println(udView.name());
+ } else {
+ System.out.println("null");
+ }
+ } catch (UnsupportedOperationException e) {
+ System.out.println("unsupported");
+ }
+ }
+
+ private static void attributeAccess(Path path) throws IOException {
BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
if (attributes != null) {
System.out.println(attributes.isRegularFile());
@@ -181,8 +331,18 @@
try {
PosixFileAttributes posixAttributes = Files.readAttributes(path, PosixFileAttributes.class);
if (posixAttributes != null) {
- System.out.println(
- posixAttributes.permissions().contains(PosixFilePermission.OWNER_READ));
+ System.out.println(posixAttributes.permissions().contains(OWNER_READ));
+ } else {
+ System.out.println("null");
+ }
+ } catch (UnsupportedOperationException e) {
+ System.out.println("unsupported");
+ }
+
+ try {
+ DosFileAttributes dosFileAttributes = Files.readAttributes(path, DosFileAttributes.class);
+ if (dosFileAttributes != null) {
+ System.out.println(dosFileAttributes.isReadOnly());
} else {
System.out.println("null");
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesVisitTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesVisitTest.java
new file mode 100644
index 0000000..4d7cccc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesVisitTest.java
@@ -0,0 +1,169 @@
+// 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.jdk11;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FilesVisitTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "Find f1",
+ "[f1.txt]",
+ "[f1.txt]",
+ "[f1.txt]",
+ "Find f4",
+ "[]",
+ "[f4.txt]",
+ "[f4.txt]",
+ "List",
+ "[f1.txt, f2.txt, f3.txt, innerDir]",
+ "Walk 1",
+ "[f1.txt, f2.txt, f3.txt, innerDir, root]",
+ "[f1.txt, f2.txt, f3.txt, innerDir, root]",
+ "Walk 7",
+ "[f1.txt, f2.txt, f3.txt, f4.txt, innerDir, root]",
+ "[f1.txt, f2.txt, f3.txt, f4.txt, innerDir, root]");
+
+ private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+ private final CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ // Skip Android 4.4.4 due to missing libjavacrypto.
+ getTestParameters()
+ .withCfRuntime(CfVm.JDK11)
+ .withDexRuntime(Version.V4_0_4)
+ .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+ .withAllApiLevels()
+ .build(),
+ ImmutableList.of(JDK11_PATH),
+ DEFAULT_SPECIFICATIONS);
+ }
+
+ public FilesVisitTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ @Test
+ public void test() throws Throwable {
+ if (parameters.isCfRuntime() && !ToolHelper.isWindows()) {
+ // Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8
+ // behaves differently on this test.
+ Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows());
+ testForJvm()
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ .withArt6Plus64BitsLib()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ public static class TestClass {
+
+ private static Path root;
+
+ public static void main(String[] args) throws IOException {
+ createDirStructure();
+ findTest();
+ listTest();
+ walkTest();
+ }
+
+ /** Creates the following structure root | f1 | f2 | f3 | innerDir | f4 */
+ private static void createDirStructure() throws IOException {
+ root = Files.createTempDirectory("rootTemp");
+ Files.createFile(root.resolve("f1.txt"));
+ Files.createFile(root.resolve("f2.txt"));
+ Files.createFile(root.resolve("f3.txt"));
+ Path innerDir = Files.createDirectory(root.resolve("innerDir"));
+ Files.createFile(root.resolve(innerDir.resolve("f4.txt")));
+ }
+
+ private static void meaningfulPrint(Stream<Path> paths) {
+ System.out.println(
+ paths
+ .map(f -> f == root ? Paths.get("root") : f)
+ .map(Path::getFileName)
+ .sorted()
+ .collect(Collectors.toList()));
+ }
+
+ private static void findTest() throws IOException {
+ System.out.println("Find f1");
+ meaningfulPrint(Files.find(root, 1, (p, a) -> p.getFileName().toString().contains("f1")));
+ meaningfulPrint(Files.find(root, 7, (p, a) -> p.getFileName().toString().contains("f1")));
+ meaningfulPrint(
+ Files.find(
+ root,
+ 7,
+ (p, a) -> p.getFileName().toString().contains("f1"),
+ FileVisitOption.FOLLOW_LINKS));
+
+ System.out.println("Find f4");
+ meaningfulPrint(Files.find(root, 1, (p, a) -> p.getFileName().toString().contains("f4")));
+ meaningfulPrint(Files.find(root, 7, (p, a) -> p.getFileName().toString().contains("f4")));
+ meaningfulPrint(
+ Files.find(
+ root,
+ 7,
+ (p, a) -> p.getFileName().toString().contains("f4"),
+ FileVisitOption.FOLLOW_LINKS));
+ }
+
+ private static void listTest() throws IOException {
+ System.out.println("List");
+ meaningfulPrint(Files.list(root));
+ }
+
+ private static void walkTest() throws IOException {
+ System.out.println("Walk 1");
+ meaningfulPrint(Files.walk(root, 1));
+ meaningfulPrint(Files.walk(root, 1, FileVisitOption.FOLLOW_LINKS));
+ System.out.println("Walk 7");
+ meaningfulPrint(Files.walk(root, 7));
+ meaningfulPrint(Files.walk(root, 7, FileVisitOption.FOLLOW_LINKS));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
index 31f87d9..9c29a9b 100644
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaInStacktraceTest.java
@@ -108,7 +108,21 @@
.getStdOut();
assertTrue(
StringUtils.splitLines(stdout).stream()
- .allMatch(s -> s.contains(isAndroidOOrLater ? "NULL" : "SourceFile")));
+ .allMatch(
+ s -> {
+ if (parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(apiLevelWithPcAsLineNumberSupport())) {
+ return s.contains("(NULL)");
+ } else if (isAndroidOOrLater) {
+ // On VMs with native support, no line info results in no source file printing.
+ // TODO(b/260384637): Create debug info for such methods to avoid this.
+ return s.equals("main(NULL)")
+ || (!s.startsWith("main") && s.contains("(SourceFile)"));
+ } else {
+ return s.contains("(SourceFile)");
+ }
+ }));
}
static class TestRunner {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
similarity index 70%
copy from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
copy to src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
index 9829235..8e6e694 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingFieldsTest.java
@@ -21,12 +21,13 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
-public class NestAttributesInDexShrinkingTest extends NestAttributesInDexTestBase
+public class NestAttributesInDexShrinkingFieldsTest extends NestAttributesInDexTestBase
implements Opcodes {
@Parameter() public TestParameters parameters;
@@ -136,59 +137,86 @@
Dump of:
public class Host {
+ private String s1 = ", ";
+ private String s2 = "!";
public static void main(String[] args) {
new Host().h1();
System.out.println();
}
static class Member1 {
- private void m(Host host) { // private or public
- host.h2("Hello");
- }
+ private String s = "Hello";
}
static class Member2 {
- private void m(Host host) { // private or public
- host.h2(", world!");
+ private String s = "world";
+
+ public void m(Host host) {
+ System.out.println(host.s1);
}
}
- private void h1() { // private or public
- new Member1().m(this);
+ public void h1() {
+ System.out.print(new Member1().s);
+ System.out.print(s1);
+ System.out.print(new Member2().s);
new Member2().m(this);
}
-
- private void h2(String message) { // private or public
- System.out.print(message);
- }
}
compiled with `-target 11`. Not a transformer here, as transforming the javac post nest
access methods is not feasible.
*/
- public static byte[] dumpHost(int methodAccess) throws Exception {
- assert methodAccess == ACC_PUBLIC || methodAccess == ACC_PRIVATE;
+ public static byte[] dumpHost(int fieldAccess) throws Exception {
+ assert fieldAccess == ACC_PUBLIC || fieldAccess == ACC_PRIVATE;
ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
MethodVisitor methodVisitor;
classWriter.visit(V11, ACC_PUBLIC | ACC_SUPER, "Host", null, "java/lang/Object", null);
+
classWriter.visitSource("Host.java", null);
+
classWriter.visitNestMember("Host$Member2");
+
classWriter.visitNestMember("Host$Member1");
+
classWriter.visitInnerClass("Host$Member2", "Host", "Member2", ACC_STATIC);
+
classWriter.visitInnerClass("Host$Member1", "Host", "Member1", ACC_STATIC);
+
+ {
+ fieldVisitor = classWriter.visitField(fieldAccess, "s1", "Ljava/lang/String;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor = classWriter.visitField(fieldAccess, "s2", "Ljava/lang/String;", null, null);
+ fieldVisitor.visitEnd();
+ }
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(1, label0);
+ methodVisitor.visitLineNumber(2, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(3, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitLdcInsn(", ");
+ methodVisitor.visitFieldInsn(PUTFIELD, "Host", "s1", "Ljava/lang/String;");
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(4, label2);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitLdcInsn("!");
+ methodVisitor.visitFieldInsn(PUTFIELD, "Host", "s2", "Ljava/lang/String;");
methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitMaxs(2, 1);
methodVisitor.visitEnd();
}
{
@@ -198,146 +226,167 @@
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitLineNumber(5, label0);
methodVisitor.visitTypeInsn(NEW, "Host");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host", "<init>", "()V", false);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host", "h1", "()V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
- methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitLineNumber(6, label1);
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "()V", false);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
- methodVisitor.visitLineNumber(5, label2);
+ methodVisitor.visitLineNumber(7, label2);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(2, 1);
methodVisitor.visitEnd();
}
{
- methodVisitor = classWriter.visitMethod(methodAccess, "h1", "()V", null, null);
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "h1", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitLineNumber(26, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitTypeInsn(NEW, "Host$Member1");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host$Member1", "<init>", "()V", false);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host$Member1", "m", "(LHost;)V", false);
+ methodVisitor.visitFieldInsn(GETFIELD, "Host$Member1", "s", "Ljava/lang/String;");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
- methodVisitor.visitLineNumber(19, label1);
+ methodVisitor.visitLineNumber(27, label1);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitFieldInsn(GETFIELD, "Host", "s1", "Ljava/lang/String;");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(28, label2);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitTypeInsn(NEW, "Host$Member2");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host$Member2", "<init>", "()V", false);
+ methodVisitor.visitFieldInsn(GETFIELD, "Host$Member2", "s", "Ljava/lang/String;");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(30, label3);
methodVisitor.visitTypeInsn(NEW, "Host$Member2");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host$Member2", "<init>", "()V", false);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host$Member2", "m", "(LHost;)V", false);
- Label label2 = new Label();
- methodVisitor.visitLabel(label2);
- methodVisitor.visitLineNumber(20, label2);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(31, label4);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ public static byte[] dumpMember1(int fieldAccess) throws Exception {
+ assert fieldAccess == ACC_PUBLIC || fieldAccess == ACC_PRIVATE;
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V11, ACC_SUPER, "Host$Member1", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Host.java", null);
+
+ classWriter.visitNestHost("Host");
+
+ classWriter.visitInnerClass("Host$Member1", "Host", "Member1", ACC_STATIC);
+
+ {
+ fieldVisitor = classWriter.visitField(fieldAccess, "s", "Ljava/lang/String;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(9, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(10, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitLdcInsn("Hello");
+ methodVisitor.visitFieldInsn(PUTFIELD, "Host$Member1", "s", "Ljava/lang/String;");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ public static byte[] dumpMember2(int fieldAccess) throws Exception {
+ assert fieldAccess == ACC_PUBLIC || fieldAccess == ACC_PRIVATE;
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V11, ACC_SUPER, "Host$Member2", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Host.java", null);
+
+ classWriter.visitNestHost("Host");
+
+ classWriter.visitInnerClass("Host$Member2", "Host", "Member2", ACC_STATIC);
+
+ {
+ fieldVisitor = classWriter.visitField(fieldAccess, "s", "Ljava/lang/String;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(16, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(17, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitLdcInsn("world");
+ methodVisitor.visitFieldInsn(PUTFIELD, "Host$Member2", "s", "Ljava/lang/String;");
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(2, 1);
methodVisitor.visitEnd();
}
{
- methodVisitor =
- classWriter.visitMethod(methodAccess, "h2", "(Ljava/lang/String;)V", null, null);
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m", "(LHost;)V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(23, label0);
+ methodVisitor.visitLineNumber(20, label0);
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitFieldInsn(GETFIELD, "Host", "s2", "Ljava/lang/String;");
methodVisitor.visitMethodInsn(
INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
- methodVisitor.visitLineNumber(24, label1);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(2, 2);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
-
- public static byte[] dumpMember1(int methodAccess) throws Exception {
- assert methodAccess == ACC_PUBLIC || methodAccess == ACC_PRIVATE;
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(V11, ACC_SUPER, "Host$Member1", null, "java/lang/Object", null);
- classWriter.visitSource("Host.java", null);
- classWriter.visitNestHost("Host");
- classWriter.visitInnerClass("Host$Member1", "Host", "Member1", ACC_STATIC);
- {
- methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(7, label0);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- {
- methodVisitor = classWriter.visitMethod(methodAccess, "m", "(LHost;)V", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(9, label0);
- methodVisitor.visitVarInsn(ALOAD, 1);
- methodVisitor.visitLdcInsn("Hello");
- methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host", "h2", "(Ljava/lang/String;)V", false);
- Label label1 = new Label();
- methodVisitor.visitLabel(label1);
- methodVisitor.visitLineNumber(10, label1);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(2, 2);
- methodVisitor.visitEnd();
- }
- classWriter.visitEnd();
-
- return classWriter.toByteArray();
- }
-
- public static byte[] dumpMember2(int methodAccess) throws Exception {
- assert methodAccess == ACC_PUBLIC || methodAccess == ACC_PRIVATE;
-
- ClassWriter classWriter = new ClassWriter(0);
- MethodVisitor methodVisitor;
-
- classWriter.visit(V11, ACC_SUPER, "Host$Member2", null, "java/lang/Object", null);
- classWriter.visitSource("Host.java", null);
- classWriter.visitNestHost("Host");
- classWriter.visitInnerClass("Host$Member2", "Host", "Member2", ACC_STATIC);
- {
- methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(13, label0);
- methodVisitor.visitVarInsn(ALOAD, 0);
- methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- methodVisitor.visitInsn(RETURN);
- methodVisitor.visitMaxs(1, 1);
- methodVisitor.visitEnd();
- }
- {
- methodVisitor = classWriter.visitMethod(methodAccess, "m", "(LHost;)V", null, null);
- methodVisitor.visitCode();
- Label label0 = new Label();
- methodVisitor.visitLabel(label0);
- methodVisitor.visitLineNumber(14, label0);
- methodVisitor.visitVarInsn(ALOAD, 1);
- methodVisitor.visitLdcInsn(", world!");
- methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host", "h2", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitLineNumber(21, label1);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java
similarity index 98%
rename from src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
rename to src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java
index 9829235..d710ad0 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingMethodsTest.java
@@ -26,7 +26,7 @@
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
-public class NestAttributesInDexShrinkingTest extends NestAttributesInDexTestBase
+public class NestAttributesInDexShrinkingMethodsTest extends NestAttributesInDexTestBase
implements Opcodes {
@Parameter() public TestParameters parameters;
diff --git a/src/test/java/com/android/tools/r8/examples/jdk9/VarHandle.java b/src/test/java/com/android/tools/r8/examples/jdk9/VarHandle.java
new file mode 100644
index 0000000..620e01b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/jdk9/VarHandle.java
@@ -0,0 +1,41 @@
+// 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.examples.jdk9;
+
+import com.android.tools.r8.examples.JavaExampleClassProxy;
+import java.nio.file.Path;
+
+public class VarHandle {
+
+ private static final String EXAMPLE_FILE = "examplesJava9/varhandle";
+
+ public static final JavaExampleClassProxy VarHandleTests =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/VarHandleTests");
+
+ public static final JavaExampleClassProxy ArrayOfInt =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/ArrayOfInt");
+ public static final JavaExampleClassProxy ArrayOfLong =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/ArrayOfLong");
+
+ public static final JavaExampleClassProxy InstanceIntField =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/InstanceIntField");
+ public static final JavaExampleClassProxy StaticIntField =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/StaticIntField");
+ public static final JavaExampleClassProxy IntFieldWithMethodHandle =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/IntFieldWithMethodHandle");
+
+ public static final JavaExampleClassProxy InstanceLongField =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/InstanceLongField");
+
+ public static final JavaExampleClassProxy InstanceObjectField =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/InstanceObjectField");
+
+ public static final JavaExampleClassProxy InstanceStringField =
+ new JavaExampleClassProxy(EXAMPLE_FILE, "varhandle/InstanceStringField");
+
+ public static Path jar() {
+ return JavaExampleClassProxy.examplesJar(EXAMPLE_FILE);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarMethodHandlesLookup.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarMethodHandlesLookup.java
new file mode 100644
index 0000000..d700033
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarMethodHandlesLookup.java
@@ -0,0 +1,126 @@
+// 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.varhandle;
+
+public final class DesugarMethodHandlesLookup {
+ public DesugarVarHandle findVarHandle(Class<?> recv, String name, Class<?> type)
+ throws NoSuchFieldException, IllegalAccessException {
+ return new DesugarVarHandle(recv, name, type);
+ }
+
+ /*
+ * Remaining methods on MethodHandles.Lookup.
+ *
+ * These could be implemented by forwarding to the runtime MethodHandles.Lookup if present at
+ * runtime.
+ *
+
+ public Class<?> accessClass(Class<?> targetClass) throws IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException,
+ IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public Class<?> defineClass(byte[] bytes) throws IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandles.Lookup dropLookupMode(int modeToDrop) {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public Class<?> findClass(String targetName) throws ClassNotFoundException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchMethodException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle findSpecial(Class<?> refc, String name, MethodType type, Class<?> specialCaller) throws NoSuchFieldException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchMethodException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchMethodException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public DesugarVarHandle findStaticVarHandle(Class<?> decl, String name, Class<?> type) throws Exception throws NoSuchFieldException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public boolean hasPrivateAccess() {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandles.Lookup in(Class<?> requestedLookupClass) {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public Class<?> lookupClass() {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public int lookupModes() {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandleInfo revealDirect(MethodHandle target) {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public String toString() {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle unreflect(Method m) throws IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle unreflectGetter(Field f) throws IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) {
+ throw new RuntimeException("Unsupported");
+ }
+
+ DesugarVarHandle unreflectVarHandle(Field f) throws IllegalAccessException {
+ throw new RuntimeException("Unsupported");
+ }
+
+ */
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
new file mode 100644
index 0000000..7e88943
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
@@ -0,0 +1,79 @@
+// 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.varhandle;
+
+import java.lang.reflect.Field;
+
+// Template class for desugaring VarHandle into com.android.tools.r8.DesugarVarHandle.
+public final class DesugarVarHandle {
+
+ // This only have methods found in libcore/libart/src/main/java/sun/misc/Unsafe.java for Lollipop.
+ private static class UnsafeStub {
+
+ public long objectFieldOffset(Field f) {
+ throw new RuntimeException("Stub called.");
+ }
+ }
+
+ private final UnsafeStub U;
+ private final Class<?> recv;
+ private final Class<?> type;
+ private final long offset;
+
+ DesugarVarHandle(Class<?> recv, String name, Class<?> type)
+ throws NoSuchFieldException, IllegalAccessException {
+ Field theUnsafe = UnsafeStub.class.getDeclaredField("theUnsafe");
+ theUnsafe.setAccessible(true);
+ U = (UnsafeStub) theUnsafe.get(null);
+ this.recv = recv;
+ Field field = recv.getDeclaredField(name);
+ this.type = field.getType();
+ this.offset = U.objectFieldOffset(recv.getDeclaredField(name));
+ }
+
+ // get variants.
+ Object get(Object ct1) {
+ // TODO(b/247076137): Implement.
+ return null;
+ }
+
+ int getInt(Object ct1) {
+ // TODO(b/247076137): Implement.
+ return -1;
+ }
+
+ long getLong(Object ct1) {
+ // TODO(b/247076137): Implement.
+ return -1L;
+ }
+
+ // set variants.
+ void set(Object ct1, Object newValue) {
+ // TODO(b/247076137): Implement.
+ }
+
+ void setInt(Object ct1, int newValue) {
+ // TODO(b/247076137): Implement.
+ }
+
+ void setLong(Object ct1, long newValue) {
+ // TODO(b/247076137): Implement.
+ }
+
+ boolean compareAndSet(Object ct1, Object expectedValue, Object newValue) {
+ // TODO(b/247076137): Implement.
+ return false;
+ }
+
+ boolean compareAndSetInt(Object ct1, int expectedValue, int newValue) {
+ // TODO(b/247076137): Implement.
+ return false;
+ }
+
+ boolean compareAndSetLong(Object ct1, long expectedValue, long newValue) {
+ // TODO(b/247076137): Implement.
+ return false;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
new file mode 100644
index 0000000..6ce480b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
@@ -0,0 +1,233 @@
+// 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.varhandle;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfTypeInstruction;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenerateVarHandleMethods extends MethodGenerationBase {
+
+ private final DexType GENERATED_TYPE =
+ factory.createType("Lcom/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods;");
+ private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
+ ImmutableList.of(DesugarMethodHandlesLookup.class, DesugarVarHandle.class);
+
+ protected final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+ }
+
+ public GenerateVarHandleMethods(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Override
+ protected DexType getGeneratedType() {
+ return GENERATED_TYPE;
+ }
+
+ @Override
+ protected List<Class<?>> getMethodTemplateClasses() {
+ return METHOD_TEMPLATE_CLASSES;
+ }
+
+ @Override
+ protected List<Class<?>> getClassesToGenerate() {
+ return ImmutableList.of(DesugarVarHandle.class, DesugarMethodHandlesLookup.class);
+ }
+
+ @Override
+ protected boolean includeMethod(DexEncodedMethod method) {
+ // Include all methods, including constructors.
+ return true;
+ }
+
+ @Override
+ protected int getYear() {
+ return 2022;
+ }
+
+ @Override
+ protected DexEncodedField getField(DexEncodedField field) {
+ if (field.getType().getTypeName().endsWith("$UnsafeStub")) {
+ return DexEncodedField.builder(field)
+ .setField(
+ factory.createField(
+ field.getHolderType(), factory.createType("Lsun/misc/Unsafe;"), field.getName()))
+ .disableAndroidApiLevelCheck()
+ .build();
+ }
+ return field;
+ }
+
+ // TODO(b/261024278): Share this code.
+ private class InstructionTypeMapper {
+ private final Map<DexType, DexType> typeMap;
+
+ InstructionTypeMapper(Map<DexType, DexType> typeMap) {
+ this.typeMap = typeMap;
+ }
+
+ private CfInstruction rewriteInstruction(CfInstruction instruction) {
+ if (instruction.isTypeInstruction()) {
+ CfInstruction rewritten = rewriteTypeInstruction(instruction.asTypeInstruction());
+ return rewritten == null ? instruction : rewritten;
+ }
+ if (instruction.isFieldInstruction()) {
+ return rewriteFieldInstruction(instruction.asFieldInstruction());
+ }
+ if (instruction.isInvoke()) {
+ return rewriteInvokeInstruction(instruction.asInvoke());
+ }
+ if (instruction.isFrame()) {
+ return rewriteFrameInstruction(instruction.asFrame());
+ }
+ return instruction;
+ }
+
+ private CfInstruction rewriteInvokeInstruction(CfInvoke instruction) {
+ CfInvoke invoke = instruction.asInvoke();
+ DexMethod method = invoke.getMethod();
+ String name = method.getName().toString();
+ DexType holderType = invoke.getMethod().getHolderType();
+ DexType rewrittenType = typeMap.getOrDefault(holderType, holderType);
+ if (rewrittenType != holderType) {
+ // TODO(b/261024278): If sharing this code also rewrite signature.
+ return new CfInvoke(
+ invoke.getOpcode(),
+ factory.createMethod(
+ rewrittenType, invoke.getMethod().getProto(), factory.createString(name)),
+ invoke.isInterface());
+ }
+ return instruction;
+ }
+
+ private CfFieldInstruction rewriteFieldInstruction(CfFieldInstruction instruction) {
+ DexType holderType = instruction.getField().getHolderType();
+ DexType rewrittenHolderType = typeMap.getOrDefault(holderType, holderType);
+ DexType fieldType = instruction.getField().getType();
+ DexType rewrittenType = typeMap.getOrDefault(fieldType, fieldType);
+ if (rewrittenHolderType != holderType || rewrittenType != fieldType) {
+ return instruction.createWithField(
+ factory.createField(rewrittenHolderType, rewrittenType, instruction.getField().name));
+ }
+ return instruction;
+ }
+
+ private CfInstruction rewriteTypeInstruction(CfTypeInstruction instruction) {
+ DexType rewrittenType = typeMap.getOrDefault(instruction.getType(), instruction.getType());
+ return rewrittenType != instruction.getType() ? instruction.withType(rewrittenType) : null;
+ }
+
+ private CfInstruction rewriteFrameInstruction(CfFrame instruction) {
+ return instruction.asFrame().mapReferenceTypes(type -> typeMap.getOrDefault(type, type));
+ }
+ }
+
+ @Override
+ protected CfCode getCode(String holderName, String methodName, CfCode code) {
+ if (methodName.endsWith("Stub")) {
+ // Don't include stubs targeted only for rewriting in the generated code.
+ return null;
+ }
+ if (!holderName.equals("DesugarVarHandle")
+ && !holderName.equals("DesugarMethodHandlesLookup")) {
+ throw new RuntimeException("Unexpected: " + holderName);
+ }
+ // Rewrite references to com.android.tools.r8.ir.desugar.varhandle.DesugarVarHandle to
+ // com.android.tools.r8.DesugarVarHandle and rewrite references to UnsafeStub to
+ // sun.misc.Unsafe.
+ InstructionTypeMapper instructionTypeMapper =
+ new InstructionTypeMapper(
+ ImmutableMap.of(
+ factory.createType(
+ "L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + ";"),
+ factory.desugarVarHandleType,
+ factory.createType(
+ "L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + "$UnsafeStub;"),
+ factory.unsafeType));
+ code.setInstructions(
+ code.getInstructions().stream()
+ .map(instructionTypeMapper::rewriteInstruction)
+ .collect(Collectors.toList()));
+ return code;
+ }
+
+ private DexEncodedMethod methodWithName(DexEncodedMethod method, String name) {
+ DexType holder = method.getHolderType();
+ DexType desugarVarHandle = factory.desugarVarHandleType;
+ DexType desugarVarHandleStub =
+ factory.createType("L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + ";");
+ // Map methods to be on the final DesugarVarHandle class.
+ if (holder == desugarVarHandleStub) {
+ holder = desugarVarHandle;
+ }
+ DexProto proto = method.getProto();
+ if (proto.getReturnType() == desugarVarHandleStub) {
+ proto = factory.createProto(desugarVarHandle, proto.parameters);
+ }
+ return DexEncodedMethod.syntheticBuilder(method)
+ .setMethod(factory.createMethod(holder, proto, factory.createString(name)))
+ .build();
+ }
+
+ @Override
+ protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
+ // Map VarHandle access mode methods to not have the Int/Long postfix.
+ for (String prefix : ImmutableList.of("get", "set", "compareAndSet")) {
+ if (method.getName().startsWith(prefix)) {
+ assert method.getName().toString().substring(prefix.length()).equals("Int")
+ || method.getName().toString().substring(prefix.length()).equals("Long")
+ || method.getName().toString().equals(prefix);
+ return methodWithName(method, prefix);
+ }
+ }
+ return methodWithName(method, method.getName().toString());
+ }
+
+ @Test
+ public void testVarHandleDesugaringGenerated() throws Exception {
+ ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+ sorted.sort(Comparator.comparing(Class::getTypeName));
+ assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+ assertEquals(
+ FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+ }
+
+ public static void main(String[] args) throws Exception {
+ new GenerateVarHandleMethods(null).generateMethodsAndWriteThemToFile();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
index 1e43777..7141ad08 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classmerger/vertical/ArrayPutToInterfaceWithObjectMergingTest.java
@@ -53,8 +53,7 @@
.addProgramClasses(I.class, A.class)
.addProgramClassFileData(getTransformedMain())
.addKeepMainRule(Main.class)
- // Keep get() to prevent that we optimize it into having static return type A.
- .addKeepRules("-keepclassmembers class " + Main.class.getTypeName() + " { *** get(...); }")
+ .addKeepMethodRules(Reference.methodFromMethod(Main.class.getDeclaredMethod("get")))
.addOptionsModification(
options ->
options
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 8983d39..4d2d2d2 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -191,7 +191,7 @@
}
@Override
- public void addThrowingInstructionToPossiblyThrowingBlock(
+ public BasicBlock addThrowingInstructionToPossiblyThrowingBlock(
IRCode code,
ListIterator<BasicBlock> blockIterator,
Instruction instruction,
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingLibraryBridgeVerticallyMergeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingLibraryBridgeVerticallyMergeTest.java
new file mode 100644
index 0000000..786df4d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingLibraryBridgeVerticallyMergeTest.java
@@ -0,0 +1,105 @@
+// 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.memberrebinding;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+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 MemberRebindingLibraryBridgeVerticallyMergeTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"Lib::foo", "B::foo"};
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(A.class, B.class, C.class, D.class, Main.class)
+ .addLibraryClasses(Lib.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, C.class, D.class, Main.class)
+ .addLibraryClasses(Lib.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addBootClasspathClasses(Lib.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, C.class, D.class, Main.class)
+ .addLibraryClasses(Lib.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules(C.class, D.class)
+ .enableInliningAnnotations()
+ .allowAccessModification()
+ .compile()
+ .addBootClasspathClasses(Lib.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Lib {
+
+ public void foo() {
+ System.out.println("Lib::foo");
+ }
+ }
+
+ public static class A extends Lib {}
+
+ public static class B extends A {
+
+ @Override
+ @NeverInline
+ public void foo() {
+ super.foo();
+ System.out.println("B::foo");
+ }
+ }
+
+ public static class C extends B {}
+
+ public static class D extends B {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ callFoo(System.currentTimeMillis() > 0 ? new C() : new B());
+ }
+
+ public static void callFoo(B b) {
+ b.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 83ea780..c544c9e 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -16,10 +16,12 @@
import com.android.tools.r8.dex.code.DexConst4;
import com.android.tools.r8.dex.code.DexConstClass;
import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
import com.android.tools.r8.dex.code.DexInvokeDirect;
import com.android.tools.r8.dex.code.DexInvokeStatic;
import com.android.tools.r8.dex.code.DexInvokeVirtual;
import com.android.tools.r8.dex.code.DexIputObject;
+import com.android.tools.r8.dex.code.DexMoveResultObject;
import com.android.tools.r8.dex.code.DexNewArray;
import com.android.tools.r8.dex.code.DexReturnVoid;
import com.android.tools.r8.dex.code.DexSgetObject;
@@ -639,7 +641,8 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep class R { *; }");
- CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+ CodeInspector inspector =
+ compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -647,19 +650,33 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- checkInstructions(
- code,
- ImmutableList.of(
- DexInvokeDirect.class,
- DexConst4.class,
- DexNewArray.class,
- DexConst4.class,
- DexConstClass.class,
- DexAputObject.class,
- DexConstString.class,
- DexInvokeStatic.class,
- DexReturnVoid.class));
- DexConstString constString = (DexConstString) code.instructions[6];
+ // Accept either array construction style (differs based on minSdkVersion).
+ if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+ checkInstructions(
+ code,
+ ImmutableList.of(
+ DexInvokeDirect.class,
+ DexConstClass.class,
+ DexFilledNewArray.class,
+ DexMoveResultObject.class,
+ DexConstString.class,
+ DexInvokeStatic.class,
+ DexReturnVoid.class));
+ } else {
+ checkInstructions(
+ code,
+ ImmutableList.of(
+ DexInvokeDirect.class,
+ DexConst4.class,
+ DexNewArray.class,
+ DexConst4.class,
+ DexConstClass.class,
+ DexAputObject.class,
+ DexConstString.class,
+ DexInvokeStatic.class,
+ DexReturnVoid.class));
+ }
+ DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
assertEquals("foo", constString.getString().toString());
}
@@ -700,7 +717,8 @@
+ "}",
"-keep class " + CLASS_NAME,
"-keep,allowobfuscation class R { *; }");
- CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
+ CodeInspector inspector =
+ compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(pgConfigs)).inspector();
ClassSubject clazz = inspector.clazz(CLASS_NAME);
assertTrue(clazz.isPresent());
@@ -708,19 +726,33 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- checkInstructions(
- code,
- ImmutableList.of(
- DexInvokeDirect.class,
- DexConst4.class,
- DexNewArray.class,
- DexConst4.class,
- DexConstClass.class,
- DexAputObject.class,
- DexConstString.class,
- DexInvokeStatic.class,
- DexReturnVoid.class));
- DexConstString constString = (DexConstString) code.instructions[6];
+ // Accept either array construction style (differs based on minSdkVersion).
+ if (code.instructions[2].getClass() == DexFilledNewArray.class) {
+ checkInstructions(
+ code,
+ ImmutableList.of(
+ DexInvokeDirect.class,
+ DexConstClass.class,
+ DexFilledNewArray.class,
+ DexMoveResultObject.class,
+ DexConstString.class,
+ DexInvokeStatic.class,
+ DexReturnVoid.class));
+ } else {
+ checkInstructions(
+ code,
+ ImmutableList.of(
+ DexInvokeDirect.class,
+ DexConst4.class,
+ DexNewArray.class,
+ DexConst4.class,
+ DexConstClass.class,
+ DexAputObject.class,
+ DexConstString.class,
+ DexInvokeStatic.class,
+ DexReturnVoid.class));
+ }
+ DexConstString constString = (DexConstString) code.instructions[code.instructions.length - 3];
assertNotEquals("foo", constString.getString().toString());
}
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
index 10fec23..5475557 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
@@ -76,10 +76,6 @@
.assertFailure();
// Extract actual stack trace and retraced stack trace from failed run result.
- // TODO(122940268): Remove test code when fixed.
- System.out.println("<--- TEST RESULT START --->");
- System.out.println(result);
- System.out.println("<--- TEST RESULT END --->");
StackTrace actualStackTrace = StackTrace.extractFromArt(result.getStdErr());
StackTrace retracedStackTrace =
actualStackTrace.retrace(result.proguardMap(), temp.newFolder().toPath());
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
index 5403673..7e6306d 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/VerticalClassMergingRetraceTest.java
@@ -129,26 +129,6 @@
compileResult -> setSyntheticMethod(compileResult, syntheticMethod));
}
- @Test
- public void testNoLineNumberTable() throws Exception {
- assumeTrue(compat);
- assumeTrue(parameters.isDexRuntime());
- Box<MethodSubject> syntheticMethod = new Box<>();
- runTest(
- ImmutableList.of(),
- (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
- StackTrace reprocessedStackTrace =
- retracedStackTrace.filter(
- stackTraceLine -> filterSynthesizedMethod(stackTraceLine, syntheticMethod.get()));
- assertThat(
- reprocessedStackTrace.filter(this::isNotDalvikNativeStartMethod),
- isSameExceptForFileNameAndLineNumber(
- expectedStackTrace.filter(this::isNotDalvikNativeStartMethod)));
- assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
- },
- compileResult -> setSyntheticMethod(compileResult, syntheticMethod));
- }
-
private void setSyntheticMethod(
R8TestCompileResult compileResult, Box<MethodSubject> syntheticMethod) throws IOException {
compileResult.inspect(
diff --git a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
index d01d917..1dab89f 100644
--- a/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b149890887/MissingLibraryTargetTest.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCompanionWithPreambleTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceCompanionWithPreambleTest.java
new file mode 100644
index 0000000..12eff52
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCompanionWithPreambleTest.java
@@ -0,0 +1,153 @@
+// 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.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.transformers.ClassFileTransformer.LineTranslation;
+import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceCompanionWithPreambleTest extends TestBase {
+
+ public enum Preamble {
+ NONE,
+ ONLY,
+ BOTH
+ }
+
+ @Parameters(name = "{0}, preamble:{1}")
+ public static List<Object[]> parameters() {
+ return buildParameters(
+ getTestParameters()
+ .withDefaultRuntimes()
+ .withApiLevel(AndroidApiLevel.B)
+ .enableApiLevelsForCf()
+ .build(),
+ Preamble.values());
+ }
+
+ private final TestParameters parameters;
+ private final Preamble preamble;
+
+ public RetraceCompanionWithPreambleTest(TestParameters parameters, Preamble preamble) {
+ this.parameters = parameters;
+ this.preamble = preamble;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(Main.class, A.class)
+ .addProgramClassFileData(getI())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkRunResult)
+ .inspectStackTrace(RetraceCompanionWithPreambleTest::checkExpectedStackTrace);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .internalEnableMappingOutput()
+ .addProgramClasses(Main.class, A.class)
+ .addProgramClassFileData(getI())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkRunResult)
+ .inspectStackTrace(RetraceCompanionWithPreambleTest::checkExpectedStackTrace);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class)
+ .addProgramClassFileData(getI())
+ .addKeepMainRule(Main.class)
+ .addKeepAttributeSourceFile()
+ .addKeepAttributeLineNumberTable()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkRunResult)
+ .inspectStackTrace(RetraceCompanionWithPreambleTest::checkExpectedStackTrace);
+ }
+
+ private void checkRunResult(SingleTestRunResult<?> runResult) {
+ runResult.assertFailureWithErrorThatMatches(containsString("Throw It!"));
+ }
+
+ private static void checkExpectedStackTrace(StackTrace stackTrace) {
+ assertThat(
+ stackTrace,
+ isSameExceptForFileNameAndLineNumber(
+ StackTrace.builder()
+ .addWithoutFileNameAndLineNumber(Main.class, "doThrow")
+ .addWithoutFileNameAndLineNumber(I.class, "foo")
+ .addWithoutFileNameAndLineNumber(Main.class, "main")
+ .build()));
+ }
+
+ private byte[] getI() throws IOException {
+ return transformer(I.class)
+ .setPredictiveLineNumbering(
+ new LineTranslation() {
+ boolean firstLine = true;
+
+ @Override
+ public int translate(MethodContext context, int line) {
+ if (!context.getReference().getMethodName().equals("foo")) {
+ return line;
+ }
+ if (preamble == Preamble.NONE) {
+ return line;
+ }
+ if (preamble == Preamble.ONLY) {
+ return -1;
+ }
+ if (firstLine) {
+ firstLine = false;
+ return -1;
+ }
+ return line;
+ }
+ })
+ .transform();
+ }
+
+ interface I {
+
+ default void foo() {
+ Main.doThrow();
+ System.out.println("more stuff");
+ }
+ }
+
+ static class A implements I {}
+
+ public static class Main {
+
+ public static void doThrow() {
+ throw new RuntimeException("Throw It!");
+ }
+
+ public static void main(String[] args) {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
index 4a76a6a..513dc73 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/ArrayWithDataLengthRewriteTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.graph.AppView;
@@ -15,6 +16,7 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -33,7 +35,7 @@
this.parameters = parameters;
}
- private static final String[] expectedOutput = {"3"};
+ private static final String[] expectedOutput = {"3", "2"};
@Test
public void d8() throws Exception {
@@ -44,7 +46,7 @@
.addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(expectedOutput)
- .inspect(this::assertNoArrayLength);
+ .inspect(i -> inspect(i, true));
}
@Test
@@ -54,32 +56,52 @@
.addProgramClasses(Main.class)
.addOptionsModification(opt -> opt.testing.irModifier = this::transformArray)
.addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(expectedOutput)
- .inspect(this::assertNoArrayLength);
+ .inspect(i -> inspect(i, false));
}
private void transformArray(IRCode irCode, AppView<?> appView) {
- if (irCode.context().getReference().getName().toString().contains("main")) {
new CodeRewriter(appView).simplifyArrayConstruction(irCode);
+ String name = irCode.context().getReference().getName().toString();
+ if (name.contains("filledArrayData")) {
assertTrue(irCode.streamInstructions().anyMatch(Instruction::isNewArrayFilledData));
+ } else if (name.contains("filledNewArray")) {
+ assertTrue(irCode.streamInstructions().anyMatch(Instruction::isInvokeNewArray));
}
}
- private void assertNoArrayLength(CodeInspector inspector) {
+ private void inspect(CodeInspector inspector, boolean d8) {
ClassSubject mainClass = inspector.clazz(Main.class);
assertTrue(mainClass.isPresent());
- assertTrue(
- mainClass.mainMethod().streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+ MethodSubject filledArrayData = mainClass.uniqueMethodWithOriginalName("filledArrayData");
+ assertTrue(filledArrayData.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+ if (!d8) {
+ MethodSubject filledNewArray = mainClass.uniqueMethodWithOriginalName("filledNewArray");
+ assertTrue(filledNewArray.streamInstructions().noneMatch(InstructionSubject::isArrayLength));
+ }
}
public static final class Main {
+ @NeverInline
+ public static void filledArrayData() {
+ short[] values = new short[3];
+ values[0] = 5;
+ values[1] = 6;
+ values[2] = 1;
+ System.out.println(values.length);
+ }
+
+ @NeverInline
+ public static void filledNewArray() {
+ int[] values = new int[] {7, 8};
+ System.out.println(values.length);
+ }
+
public static void main(String[] args) {
- int[] ints = new int[3];
- ints[0] = 5;
- ints[1] = 6;
- ints[2] = 1;
- System.out.println(ints.length);
+ filledArrayData();
+ filledNewArray();
}
}
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java
new file mode 100644
index 0000000..3e04138
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/FilledArrayDataWithCatchHandlerTest.java
@@ -0,0 +1,106 @@
+// 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.rewrite.arrays;
+
+import static com.android.tools.r8.cf.methodhandles.fields.ClassFieldMethodHandleTest.Main.assertEquals;
+import static org.junit.Assert.assertTrue;
+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.dex.code.DexFillArrayData;
+import com.android.tools.r8.dex.code.DexNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FilledArrayDataWithCatchHandlerTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public FilledArrayDataWithCatchHandlerTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(FilledArrayDataWithCatchHandlerTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(FilledArrayDataWithCatchHandlerTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkInstructions);
+ }
+
+ private void checkInstructions(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ List<InstructionSubject> newArrays =
+ foo.streamInstructions()
+ .filter(i -> i.asDexInstruction().getInstruction() instanceof DexNewArray)
+ .collect(Collectors.toList());
+ assertEquals(1, newArrays.size());
+
+ List<InstructionSubject> fillArrays =
+ foo.streamInstructions()
+ .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFillArrayData)
+ .collect(Collectors.toList());
+ assertEquals(1, fillArrays.size());
+
+ InstructionOffsetSubject offsetNew = newArrays.get(0).getOffset(foo);
+ InstructionOffsetSubject offsetFill = newArrays.get(0).getOffset(foo);
+ assertTrue(
+ foo.streamTryCatches()
+ .allMatch(r -> r.getRange().includes(offsetNew) && r.getRange().includes(offsetFill)));
+ }
+
+ static class TestClass {
+
+ public static int foo() {
+ int value = 1;
+ int[] array = null;
+ try {
+ array = new int[6];
+ } catch (RuntimeException e) {
+ return array[0];
+ }
+ array[0] = value;
+ array[1] = value;
+ array[2] = value;
+ array[3] = value;
+ array[4] = value;
+ array[5] = value;
+ return array[5];
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
index 8cc0379..0003703 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
@@ -3,17 +3,25 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.rewrite.arrays;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
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.dex.code.DexFilledNewArray;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-// Regression test for issue found in b/259986613
@RunWith(Parameterized.class)
public class NewArrayInCatchRangeTest extends TestBase {
@@ -46,19 +54,32 @@
.setMinApi(parameters.getApiLevel())
.addInnerClasses(NewArrayInCatchRangeTest.class)
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkInstructions);
+ }
+
+ private void checkInstructions(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ List<InstructionSubject> filledArrayInstructions =
+ foo.streamInstructions()
+ .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+ .collect(Collectors.toList());
+ assertEquals(1, filledArrayInstructions.size());
+ InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
+ assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
}
static class TestClass {
public static int foo() {
int value = 1;
- int[] array = new int[1];
+ int[] array = null;
try {
- array[0] = value;
- } catch (RuntimeException e) {
- return array[0];
+ array = new int[1];
+ } catch (Exception e) {
+ return array == null ? -1 : array.length;
}
+ array[0] = value;
return array[0];
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java
new file mode 100644
index 0000000..5ad8f6b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInSameCatchRangeTest.java
@@ -0,0 +1,81 @@
+// 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.rewrite.arrays;
+
+import static org.junit.Assert.assertTrue;
+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.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArrayInSameCatchRangeTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArrayInSameCatchRangeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArrayInSameCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArrayInSameCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkHasFilledNewArray);
+ }
+
+ private void checkHasFilledNewArray(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ assertTrue(
+ foo.streamInstructions()
+ .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
+ }
+
+ static class TestClass {
+
+ public static int foo() {
+ int value = 1;
+ try {
+ int[] array = new int[2];
+ array[0] = value;
+ array[1] = value;
+ return System.nanoTime() > 0 ? array[0] : 2;
+ } catch (RuntimeException e) {
+ return 42;
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
index ab11b3d..3996647 100644
--- a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
@@ -3,12 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.rewrite.arrays;
+import static org.junit.Assert.assertTrue;
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.dex.code.DexFilledNewArray;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -45,7 +49,15 @@
.setMinApi(parameters.getApiLevel())
.addInnerClasses(NewArrayInTwoCatchRangesTest.class)
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkHasFilledNewArray);
+ }
+
+ private void checkHasFilledNewArray(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ assertTrue(
+ foo.streamInstructions()
+ .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
new file mode 100644
index 0000000..40d1557
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayMonitorTest.java
@@ -0,0 +1,89 @@
+// 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.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+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.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArrayMonitorTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArrayMonitorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArrayMonitorTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArrayMonitorTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkInstructions);
+ }
+
+ private void checkInstructions(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ List<InstructionSubject> filledArrayInstructions =
+ foo.streamInstructions()
+ .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+ .collect(Collectors.toList());
+ assertEquals(1, filledArrayInstructions.size());
+ InstructionOffsetSubject offset = filledArrayInstructions.get(0).getOffset(foo);
+ assertTrue(foo.streamTryCatches().allMatch(r -> r.getRange().includes(offset)));
+ }
+
+ static class TestClass {
+
+ public static synchronized int foo() {
+ int value = 1;
+ int[] array = new int[1];
+ try {
+ array[0] = value;
+ } catch (RuntimeException e) {
+ return array[0];
+ }
+ return array[0];
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
new file mode 100644
index 0000000..d7dc866
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayPutInCatchRangeTest.java
@@ -0,0 +1,85 @@
+// 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.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+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.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collections;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// Regression test for issue found in b/259986613
+@RunWith(Parameterized.class)
+public class NewArrayPutInCatchRangeTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArrayPutInCatchRangeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArrayPutInCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArrayPutInCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkHasFilledNewArray);
+ }
+
+ private void checkHasFilledNewArray(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ assertTrue(
+ foo.streamInstructions()
+ .anyMatch(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray));
+ assertEquals(Collections.emptyList(), foo.streamTryCatches().collect(Collectors.toList()));
+ }
+
+ static class TestClass {
+
+ public static int foo() {
+ int value = 1;
+ int[] array = new int[1];
+ try {
+ array[0] = value;
+ } catch (RuntimeException e) {
+ return array[0];
+ }
+ return array[0];
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
new file mode 100644
index 0000000..3319ccc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArraySynchronizedBlockTest.java
@@ -0,0 +1,84 @@
+// 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.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+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.dex.code.DexFilledNewArray;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArraySynchronizedBlockTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArraySynchronizedBlockTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArraySynchronizedBlockTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArraySynchronizedBlockTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkInstructions);
+ }
+
+ private void checkInstructions(CodeInspector inspector) {
+ MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithFinalName("foo");
+ List<InstructionSubject> filledArrayInstructions =
+ foo.streamInstructions()
+ .filter(i -> i.asDexInstruction().getInstruction() instanceof DexFilledNewArray)
+ .collect(Collectors.toList());
+ assertEquals(0, filledArrayInstructions.size());
+ }
+
+ static class TestClass {
+
+ public static int foo() {
+ int value = 1;
+ int[] array;
+ synchronized (TestClass.class) {
+ array = new int[1];
+ } // monitor exit here prohibits optimization as its failure could observe the lack of init.
+ array[0] = value;
+ return array[0];
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
new file mode 100644
index 0000000..611ed3f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/SimplifyArrayConstructionTest.java
@@ -0,0 +1,774 @@
+// 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.rewrite.arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.dex.code.DexFillArrayData;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.dex.code.DexFilledNewArrayRange;
+import com.android.tools.r8.dex.code.DexNewArray;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.beust.jcommander.internal.Lists;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SimplifyArrayConstructionTest extends TestBase {
+ @Parameters(name = "{0}, mode = {1}")
+ public static Iterable<?> data() {
+ return buildParameters(
+ getTestParameters().withDefaultCfRuntime().withDexRuntimesAndAllApiLevels().build(),
+ CompilationMode.values());
+ }
+
+ private final TestParameters parameters;
+ private final CompilationMode compilationMode;
+
+ public SimplifyArrayConstructionTest(TestParameters parameters, CompilationMode compilationMode) {
+ this.parameters = parameters;
+ this.compilationMode = compilationMode;
+ }
+
+ private static final Class<?>[] DEX_ARRAY_INSTRUCTIONS = {
+ DexNewArray.class, DexFilledNewArray.class, DexFilledNewArrayRange.class, DexFillArrayData.class
+ };
+
+ private static final String[] EXPECTED_OUTPUT = {
+ "[a]",
+ "[a, 1, null]",
+ "[1, null]",
+ "[1, null, 2]",
+ "[1, null, 2]",
+ "[1]",
+ "[1, 2]",
+ "[1, 2, 3, 4, 5]",
+ "[1]",
+ "[a, 1, null, d, e, f]",
+ "[1, null, 3, null, null, 6]",
+ "[1, 2, 3, 4, 5, 6]",
+ "[true, false]",
+ "[1, 2]",
+ "[1, 2]",
+ "[1, 2]",
+ "[1.0, 2.0]",
+ "[1.0, 2.0]",
+ "[]",
+ "[]",
+ "[true]",
+ "[1]",
+ "[1]",
+ "[1]",
+ "[1.0]",
+ "[1.0]",
+ "[0, 1]",
+ "[1, null]",
+ "[a]",
+ "[0, 1]",
+ "200",
+ "[0, 1, 2, 3, 4]",
+ "[4, 0, 0, 0, 0]",
+ "[4, 1, 2, 3, 4]",
+ "[0, 1, 2]",
+ "[0]",
+ "[0, 1, 2]",
+ "[1, 2, 3]",
+ "[1, 2, 3, 4, 5, 6]",
+ "[0]",
+ "[null, null]",
+ };
+
+ private static final byte[] TRANSFORMED_MAIN = transformedMain();
+
+ @Test
+ public void testRuntime() throws Exception {
+ assumeFalse(compilationMode == CompilationMode.DEBUG);
+ testForRuntime(
+ parameters.getRuntime(),
+ d8TestBuilder ->
+ d8TestBuilder.setMinApi(parameters.getApiLevel()).setMode(compilationMode))
+ .addProgramClassFileData(TRANSFORMED_MAIN)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+ .inspect(inspector -> inspect(inspector, false));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options ->
+ options
+ .getOpenClosedInterfacesOptions()
+ .suppressSingleOpenInterface(Reference.classFromClass(Serializable.class)))
+ .setMode(compilationMode)
+ .addProgramClassFileData(TRANSFORMED_MAIN)
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .addKeepAnnotation()
+ .addKeepRules("-keepclassmembers class * { @com.android.tools.r8.Keep *; }")
+ .addKeepRules("-assumenosideeffects class * { *** assumedNullField return null; }")
+ .addKeepRules("-assumenosideeffects class * { *** assumedNonNullField return _NONNULL_; }")
+ .addDontObfuscate()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT)
+ .inspect(inspector -> inspect(inspector, true));
+ }
+
+ private static byte[] transformedMain() {
+ try {
+ return transformer(Main.class)
+ .transformMethodInsnInMethod(
+ "interfaceArrayWithRawObject",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (name.equals("getObjectThatImplementsSerializable")) {
+ visitor.visitMethodInsn(opcode, owner, name, "()Ljava/lang/Object;", isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .setReturnType(
+ ClassFileTransformer.MethodPredicate.onName("getObjectThatImplementsSerializable"),
+ Object.class.getTypeName())
+ .transform();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void inspect(CodeInspector inspector, boolean isR8) {
+ if (parameters.isCfRuntime()) {
+ return;
+ }
+ ClassSubject mainClass = inspector.clazz(Main.class);
+ assertTrue(mainClass.isPresent());
+
+ MethodSubject stringArrays = mainClass.uniqueMethodWithOriginalName("stringArrays");
+ MethodSubject referenceArraysNoCasts =
+ mainClass.uniqueMethodWithOriginalName("referenceArraysNoCasts");
+ MethodSubject referenceArraysWithSubclasses =
+ mainClass.uniqueMethodWithOriginalName("referenceArraysWithSubclasses");
+ MethodSubject interfaceArrayWithRawObject =
+ mainClass.uniqueMethodWithOriginalName("interfaceArrayWithRawObject");
+
+ MethodSubject phiFilledNewArray = mainClass.uniqueMethodWithOriginalName("phiFilledNewArray");
+ MethodSubject intsThatUseFilledNewArray =
+ mainClass.uniqueMethodWithOriginalName("intsThatUseFilledNewArray");
+ MethodSubject twoDimensionalArrays =
+ mainClass.uniqueMethodWithOriginalName("twoDimensionalArrays");
+ MethodSubject objectArraysFilledNewArrayRange =
+ mainClass.uniqueMethodWithOriginalName("objectArraysFilledNewArrayRange");
+ MethodSubject arraysThatUseFilledData =
+ mainClass.uniqueMethodWithOriginalName("arraysThatUseFilledData");
+ MethodSubject arraysThatUseNewArrayEmpty =
+ mainClass.uniqueMethodWithOriginalName("arraysThatUseNewArrayEmpty");
+ MethodSubject reversedArray = mainClass.uniqueMethodWithOriginalName("reversedArray");
+ MethodSubject arrayWithCorrectCountButIncompleteCoverage =
+ mainClass.uniqueMethodWithOriginalName("arrayWithCorrectCountButIncompleteCoverage");
+ MethodSubject arrayWithExtraInitialPuts =
+ mainClass.uniqueMethodWithOriginalName("arrayWithExtraInitialPuts");
+ MethodSubject catchHandlerThrowing =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerThrowing");
+ MethodSubject catchHandlerNonThrowingFilledNewArray =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFilledNewArray");
+ MethodSubject catchHandlerNonThrowingFillArrayData =
+ mainClass.uniqueMethodWithOriginalName("catchHandlerNonThrowingFillArrayData");
+ MethodSubject assumedValues = mainClass.uniqueMethodWithOriginalName("assumedValues");
+
+ assertArrayTypes(arraysThatUseNewArrayEmpty, DexNewArray.class);
+ assertArrayTypes(intsThatUseFilledNewArray, DexFilledNewArray.class);
+ assertFilledArrayData(arraysThatUseFilledData);
+ assertFilledArrayData(catchHandlerNonThrowingFillArrayData);
+
+ if (compilationMode == CompilationMode.DEBUG) {
+ // The explicit assignments can't be collapsed without breaking the debugger's ability to
+ // visit each line.
+ assertArrayTypes(reversedArray, DexNewArray.class);
+ } else {
+ assertArrayTypes(reversedArray, DexFilledNewArray.class);
+ }
+
+ // Cannot use filled-new-array of String before K.
+ if (parameters.getApiLevel().isLessThan(AndroidApiLevel.K)) {
+ assertArrayTypes(stringArrays, DexNewArray.class);
+ } else {
+ assertArrayTypes(stringArrays, DexFilledNewArray.class);
+ }
+ // Cannot use filled-new-array of Object before L.
+ if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+ assertArrayTypes(referenceArraysNoCasts, DexNewArray.class);
+ assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+ assertArrayTypes(phiFilledNewArray, DexNewArray.class);
+ assertArrayTypes(objectArraysFilledNewArrayRange, DexNewArray.class);
+ assertArrayTypes(twoDimensionalArrays, DexNewArray.class);
+ assertArrayTypes(assumedValues, DexNewArray.class);
+ } else {
+ assertArrayTypes(referenceArraysNoCasts, DexFilledNewArray.class);
+ // TODO(b/246971330): Add support for arrays with subtypes.
+ if (isR8) {
+ assertArrayTypes(referenceArraysWithSubclasses, DexFilledNewArray.class);
+ } else {
+ assertArrayTypes(referenceArraysWithSubclasses, DexNewArray.class);
+ }
+
+ // TODO(b/246971330): Add support for arrays whose values have conditionals.
+ // assertArrayTypes(phiFilledNewArray, DexFilledNewArray.class);
+
+ assertArrayTypes(objectArraysFilledNewArrayRange, DexFilledNewArrayRange.class);
+
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
+ assertArrayTypes(twoDimensionalArrays, DexFilledNewArray.class);
+ } else {
+ // No need to assert this case. If it's wrong, Dalvik verify errors cause test failures.
+ }
+
+ assertArrayTypes(assumedValues, DexFilledNewArray.class);
+ }
+ // filled-new-array fails verification when types of parameters are not subclasses (aput-object
+ // does not).
+ assertArrayTypes(interfaceArrayWithRawObject, DexNewArray.class);
+
+ // These could be optimized to use InvokeNewArray, but they seem like rare code patterns so we
+ // haven't bothered.
+ assertArrayTypes(arrayWithExtraInitialPuts, DexNewArray.class);
+ assertArrayTypes(arrayWithCorrectCountButIncompleteCoverage, DexNewArray.class);
+
+ assertArrayTypes(catchHandlerThrowing, DexNewArray.class);
+ assertArrayTypes(catchHandlerNonThrowingFilledNewArray, DexFilledNewArray.class);
+ }
+
+ private static Predicate<InstructionSubject> isInstruction(List<Class<?>> allowlist) {
+ return (ins) -> allowlist.contains(ins.asDexInstruction().getInstruction().getClass());
+ }
+
+ private static Predicate<InstructionSubject> isInstruction(Class<?> clazz) {
+ return isInstruction(Arrays.asList(clazz));
+ }
+
+ private static void assertArrayTypes(MethodSubject method, Class<?> allowedArrayInst) {
+ assertTrue(method.isPresent());
+ List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+ disallowedClasses.remove(allowedArrayInst);
+
+ assertTrue(method.streamInstructions().anyMatch(isInstruction(allowedArrayInst)));
+ assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+ }
+
+ private static void assertFilledArrayData(MethodSubject method) {
+ assertTrue(method.isPresent());
+ List<Class<?>> disallowedClasses = Lists.newArrayList(DEX_ARRAY_INSTRUCTIONS);
+ disallowedClasses.remove(DexFillArrayData.class);
+ disallowedClasses.remove(DexNewArray.class);
+
+ assertTrue(method.streamInstructions().noneMatch(isInstruction(disallowedClasses)));
+ assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isArrayPut));
+ long numNewArray = method.streamInstructions().filter(InstructionSubject::isNewArray).count();
+ long numFillArray =
+ method.streamInstructions().filter(isInstruction(DexFillArrayData.class)).count();
+ assertEquals(numNewArray, numFillArray);
+ }
+
+ public static final class Main {
+ static final String assumedNonNullField = null;
+ static final String assumedNullField = null;
+
+ public static void main(String[] args) {
+ stringArrays();
+ referenceArraysNoCasts();
+ referenceArraysWithSubclasses();
+ interfaceArrayWithRawObject();
+ phiFilledNewArray();
+ intsThatUseFilledNewArray();
+ twoDimensionalArrays();
+ objectArraysFilledNewArrayRange();
+ arraysThatUseFilledData();
+ arraysThatUseNewArrayEmpty();
+ reversedArray();
+ arrayWithCorrectCountButIncompleteCoverage();
+ arrayWithExtraInitialPuts();
+ catchHandlerThrowing();
+ catchHandlerNonThrowingFilledNewArray();
+ catchHandlerNonThrowingFillArrayData();
+ arrayIntoAnotherArray();
+ assumedValues();
+ }
+
+ @NeverInline
+ private static void stringArrays() {
+ // Test exact class, no null.
+ String[] stringArr = {"a"};
+ System.out.println(Arrays.toString(stringArr));
+ }
+
+ @NeverInline
+ private static void referenceArraysNoCasts() {
+ // Tests that no type info is needed when array type is Object[].
+ Object[] objectArr = {"a", 1, null};
+ System.out.println(Arrays.toString(objectArr));
+ // Test that interface arrays work when we have the exact interface already.
+ Serializable[] interfaceArr = {getSerializable(1), null};
+ System.out.println(Arrays.toString(interfaceArr));
+ }
+
+ @Keep
+ private static Serializable getSerializable(Integer value) {
+ return value;
+ }
+
+ @NeverInline
+ private static void referenceArraysWithSubclasses() {
+ Serializable[] interfaceArr = {1, null, 2};
+ System.out.println(Arrays.toString(interfaceArr));
+ Number[] objArray = {1, null, 2};
+ System.out.println(Arrays.toString(objArray));
+ }
+
+ @NeverInline
+ private static void interfaceArrayWithRawObject() {
+ // Interfaces can use filled-new-array only when we know types implement the interface.
+ Serializable[] arr = new Serializable[1];
+ // Transformed from `I get()` to `Object get()`.
+ arr[0] = getObjectThatImplementsSerializable();
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @Keep
+ private static /*Object*/ Serializable getObjectThatImplementsSerializable() {
+ return 1;
+ }
+
+ @NeverInline
+ private static void reversedArray() {
+ int[] arr = new int[5];
+ arr[4] = 4;
+ arr[3] = 3;
+ arr[2] = 2;
+ arr[1] = 1;
+ arr[0] = 0;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void arrayWithCorrectCountButIncompleteCoverage() {
+ int[] arr = new int[5];
+ arr[0] = 0;
+ arr[0] = 1;
+ arr[0] = 2;
+ arr[0] = 3;
+ arr[0] = 4;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void arrayWithExtraInitialPuts() {
+ int[] arr = new int[5];
+ arr[0] = 0;
+ arr[0] = 1;
+ arr[0] = 2;
+ arr[0] = 3;
+ arr[0] = 4;
+ arr[1] = 1;
+ arr[2] = 2;
+ arr[3] = 3;
+ arr[4] = 4;
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void catchHandlerNonThrowingFilledNewArray() {
+ try {
+ int[] arr1 = {1, 2, 3};
+ System.currentTimeMillis();
+ System.out.println(Arrays.toString(arr1));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ @NeverInline
+ private static void catchHandlerNonThrowingFillArrayData() {
+ try {
+ int[] arr = {1, 2, 3, 4, 5, 6};
+ System.currentTimeMillis();
+ System.out.println(Arrays.toString(arr));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ @NeverInline
+ private static void catchHandlerThrowing() {
+ int[] arr1 = new int[3];
+ arr1[0] = 0;
+ arr1[1] = 1;
+ // Since the array is used in only one spot, and that spot is not within the try/catch, it
+ // should be safe to use filled-new-array, but we don't.
+ try {
+ System.currentTimeMillis();
+ arr1[2] = 2;
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ System.out.println(Arrays.toString(arr1));
+
+ try {
+ // Test filled-new-array with a throwing instruction before the last array-put.
+ int[] arr2 = new int[1];
+ System.currentTimeMillis();
+ arr2[0] = 0;
+ System.out.println(Arrays.toString(arr2));
+
+ // Test filled-array-data with a throwing instruction before the last array-put.
+ short[] arr3 = new short[3];
+ arr3[0] = 0;
+ arr3[1] = 1;
+ System.currentTimeMillis();
+ arr3[2] = 2;
+ System.out.println(Arrays.toString(arr3));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ @NeverInline
+ private static void arrayIntoAnotherArray() {
+ // Tests that our computeValues() method does not assume all array-put-object users have
+ // the array as the target.
+ int[] intArr = new int[1];
+ Object[] objArr = new Object[2];
+ objArr[0] = intArr;
+ System.out.println(Arrays.toString((int[]) objArr[0]));
+ }
+
+ @NeverInline
+ private static void assumedValues() {
+ Object[] arr = {assumedNonNullField, assumedNullField};
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @NeverInline
+ private static void phiFilledNewArray() {
+ // The presence of ? should not affect use of filled-new-array.
+ Integer[] phiArray = {1, System.nanoTime() > 0 ? 2 : 3};
+ System.out.println(Arrays.toString(phiArray));
+ }
+
+ @NeverInline
+ private static void intsThatUseFilledNewArray() {
+ // Up to 5 ints uses filled-new-array rather than filled-array-data.
+ int[] intArr = {1, 2, 3, 4, 5};
+ System.out.println(Arrays.toString(intArr));
+ }
+
+ @NeverInline
+ private static void twoDimensionalArrays() {
+ Integer[][] twoDimensions = {new Integer[] {1}, null};
+ System.out.println(Arrays.toString(Arrays.asList(twoDimensions).get(0)));
+ }
+
+ @NeverInline
+ private static void objectArraysFilledNewArrayRange() {
+ // 6 or more elements use /range variant.
+ Object[] objectArr = {"a", 1, null, "d", "e", "f"};
+ System.out.println(Arrays.toString(objectArr));
+ Serializable[] interfaceArr = {
+ getSerializable(1), null, getSerializable(3), null, null, getSerializable(6)
+ };
+ System.out.println(Arrays.toString(interfaceArr));
+ }
+
+ @NeverInline
+ private static void arraysThatUseFilledData() {
+ // For int[], <= 5 elements should use NewArrayFilledData (otherwise NewFilledArray is used).
+ int[] intArr = {1, 2, 3, 4, 5, 6};
+ // For other primitives, > 1 element leads to fill-array-data.
+ System.out.println(Arrays.toString(intArr));
+ boolean[] boolArr = {true, false};
+ System.out.println(Arrays.toString(boolArr));
+ byte[] byteArr = {1, 2};
+ System.out.println(Arrays.toString(byteArr));
+ char[] charArr = {'1', '2'};
+ System.out.println(Arrays.toString(charArr));
+ long[] longArr = {1, 2};
+ System.out.println(Arrays.toString(longArr));
+ float[] floatArr = {1, 2};
+ System.out.println(Arrays.toString(floatArr));
+ double[] doubleArr = {1, 2};
+ System.out.println(Arrays.toString(doubleArr));
+ }
+
+ @NeverInline
+ private static void arraysThatUseNewArrayEmpty() {
+ // int/object of size zero should not use filled-new-array.
+ int[] intArr = {};
+ System.out.println(Arrays.toString(intArr));
+ String[] strArr = {};
+ System.out.println(Arrays.toString(strArr));
+
+ // Other primitives with size <= 1 should not use filled-array-data.
+ boolean[] boolArr = {true};
+ System.out.println(Arrays.toString(boolArr));
+ byte[] byteArr = {1};
+ System.out.println(Arrays.toString(byteArr));
+ char[] charArr = {'1'};
+ System.out.println(Arrays.toString(charArr));
+ long[] longArr = {1};
+ System.out.println(Arrays.toString(longArr));
+ float[] floatArr = {1};
+ System.out.println(Arrays.toString(floatArr));
+ double[] doubleArr = {1};
+ System.out.println(Arrays.toString(doubleArr));
+
+ // Array used before all members are set.
+ int[] readArray = new int[2];
+ readArray[0] = (int) (System.currentTimeMillis() / System.nanoTime());
+ readArray[1] = readArray[0] + 1;
+ System.out.println(Arrays.toString(readArray));
+
+ // Array does not have all elements set (we could make this work, but likely this is rare).
+ Integer[] partialArray = new Integer[2];
+ partialArray[0] = 1;
+ System.out.println(Arrays.toString(partialArray));
+
+ // Non-constant array size.
+ int trickyZero = (int) (System.currentTimeMillis() / System.nanoTime());
+ Object[] nonConstSize = new Object[trickyZero + 1];
+ nonConstSize[0] = "a";
+ System.out.println(Arrays.toString(nonConstSize));
+
+ // Non-constant index.
+ Object[] nonConstIndex = new Object[2];
+ nonConstIndex[trickyZero] = 0;
+ nonConstIndex[trickyZero + 1] = 1;
+ System.out.println(Arrays.toString(nonConstIndex));
+
+ // Exceeds our (arbitrary) size limit for /range.
+ String[] bigArr = new String[201];
+ bigArr[0] = "0";
+ bigArr[1] = "1";
+ bigArr[2] = "2";
+ bigArr[3] = "3";
+ bigArr[4] = "4";
+ bigArr[5] = "5";
+ bigArr[6] = "6";
+ bigArr[7] = "7";
+ bigArr[8] = "8";
+ bigArr[9] = "9";
+ bigArr[10] = "10";
+ bigArr[11] = "11";
+ bigArr[12] = "12";
+ bigArr[13] = "13";
+ bigArr[14] = "14";
+ bigArr[15] = "15";
+ bigArr[16] = "16";
+ bigArr[17] = "17";
+ bigArr[18] = "18";
+ bigArr[19] = "19";
+ bigArr[20] = "20";
+ bigArr[21] = "21";
+ bigArr[22] = "22";
+ bigArr[23] = "23";
+ bigArr[24] = "24";
+ bigArr[25] = "25";
+ bigArr[26] = "26";
+ bigArr[27] = "27";
+ bigArr[28] = "28";
+ bigArr[29] = "29";
+ bigArr[30] = "30";
+ bigArr[31] = "31";
+ bigArr[32] = "32";
+ bigArr[33] = "33";
+ bigArr[34] = "34";
+ bigArr[35] = "35";
+ bigArr[36] = "36";
+ bigArr[37] = "37";
+ bigArr[38] = "38";
+ bigArr[39] = "39";
+ bigArr[40] = "40";
+ bigArr[41] = "41";
+ bigArr[42] = "42";
+ bigArr[43] = "43";
+ bigArr[44] = "44";
+ bigArr[45] = "45";
+ bigArr[46] = "46";
+ bigArr[47] = "47";
+ bigArr[48] = "48";
+ bigArr[49] = "49";
+ bigArr[50] = "50";
+ bigArr[51] = "51";
+ bigArr[52] = "52";
+ bigArr[53] = "53";
+ bigArr[54] = "54";
+ bigArr[55] = "55";
+ bigArr[56] = "56";
+ bigArr[57] = "57";
+ bigArr[58] = "58";
+ bigArr[59] = "59";
+ bigArr[60] = "60";
+ bigArr[61] = "61";
+ bigArr[62] = "62";
+ bigArr[63] = "63";
+ bigArr[64] = "64";
+ bigArr[65] = "65";
+ bigArr[66] = "66";
+ bigArr[67] = "67";
+ bigArr[68] = "68";
+ bigArr[69] = "69";
+ bigArr[70] = "70";
+ bigArr[71] = "71";
+ bigArr[72] = "72";
+ bigArr[73] = "73";
+ bigArr[74] = "74";
+ bigArr[75] = "75";
+ bigArr[76] = "76";
+ bigArr[77] = "77";
+ bigArr[78] = "78";
+ bigArr[79] = "79";
+ bigArr[80] = "80";
+ bigArr[81] = "81";
+ bigArr[82] = "82";
+ bigArr[83] = "83";
+ bigArr[84] = "84";
+ bigArr[85] = "85";
+ bigArr[86] = "86";
+ bigArr[87] = "87";
+ bigArr[88] = "88";
+ bigArr[89] = "89";
+ bigArr[90] = "90";
+ bigArr[91] = "91";
+ bigArr[92] = "92";
+ bigArr[93] = "93";
+ bigArr[94] = "94";
+ bigArr[95] = "95";
+ bigArr[96] = "96";
+ bigArr[97] = "97";
+ bigArr[98] = "98";
+ bigArr[99] = "99";
+ bigArr[100] = "100";
+ bigArr[101] = "101";
+ bigArr[102] = "102";
+ bigArr[103] = "103";
+ bigArr[104] = "104";
+ bigArr[105] = "105";
+ bigArr[106] = "106";
+ bigArr[107] = "107";
+ bigArr[108] = "108";
+ bigArr[109] = "109";
+ bigArr[110] = "110";
+ bigArr[111] = "111";
+ bigArr[112] = "112";
+ bigArr[113] = "113";
+ bigArr[114] = "114";
+ bigArr[115] = "115";
+ bigArr[116] = "116";
+ bigArr[117] = "117";
+ bigArr[118] = "118";
+ bigArr[119] = "119";
+ bigArr[120] = "120";
+ bigArr[121] = "121";
+ bigArr[122] = "122";
+ bigArr[123] = "123";
+ bigArr[124] = "124";
+ bigArr[125] = "125";
+ bigArr[126] = "126";
+ bigArr[127] = "127";
+ bigArr[128] = "128";
+ bigArr[129] = "129";
+ bigArr[130] = "130";
+ bigArr[131] = "131";
+ bigArr[132] = "132";
+ bigArr[133] = "133";
+ bigArr[134] = "134";
+ bigArr[135] = "135";
+ bigArr[136] = "136";
+ bigArr[137] = "137";
+ bigArr[138] = "138";
+ bigArr[139] = "139";
+ bigArr[140] = "140";
+ bigArr[141] = "141";
+ bigArr[142] = "142";
+ bigArr[143] = "143";
+ bigArr[144] = "144";
+ bigArr[145] = "145";
+ bigArr[146] = "146";
+ bigArr[147] = "147";
+ bigArr[148] = "148";
+ bigArr[149] = "149";
+ bigArr[150] = "150";
+ bigArr[151] = "151";
+ bigArr[152] = "152";
+ bigArr[153] = "153";
+ bigArr[154] = "154";
+ bigArr[155] = "155";
+ bigArr[156] = "156";
+ bigArr[157] = "157";
+ bigArr[158] = "158";
+ bigArr[159] = "159";
+ bigArr[160] = "160";
+ bigArr[161] = "161";
+ bigArr[162] = "162";
+ bigArr[163] = "163";
+ bigArr[164] = "164";
+ bigArr[165] = "165";
+ bigArr[166] = "166";
+ bigArr[167] = "167";
+ bigArr[168] = "168";
+ bigArr[169] = "169";
+ bigArr[170] = "170";
+ bigArr[171] = "171";
+ bigArr[172] = "172";
+ bigArr[173] = "173";
+ bigArr[174] = "174";
+ bigArr[175] = "175";
+ bigArr[176] = "176";
+ bigArr[177] = "177";
+ bigArr[178] = "178";
+ bigArr[179] = "179";
+ bigArr[180] = "180";
+ bigArr[181] = "181";
+ bigArr[182] = "182";
+ bigArr[183] = "183";
+ bigArr[184] = "184";
+ bigArr[185] = "185";
+ bigArr[186] = "186";
+ bigArr[187] = "187";
+ bigArr[188] = "188";
+ bigArr[189] = "189";
+ bigArr[190] = "190";
+ bigArr[191] = "191";
+ bigArr[192] = "192";
+ bigArr[193] = "193";
+ bigArr[194] = "194";
+ bigArr[195] = "195";
+ bigArr[196] = "196";
+ bigArr[197] = "197";
+ bigArr[198] = "198";
+ bigArr[199] = "199";
+ bigArr[200] = "200";
+ System.out.println(Arrays.asList(bigArr).get(200));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 70e7eab..9ec695c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -122,6 +122,31 @@
};
}
+ public static Matcher<MethodSubject> containsConstClass(ClassReference classReference) {
+ return new TypeSafeMatcher<MethodSubject>() {
+ @Override
+ protected boolean matchesSafely(MethodSubject subject) {
+ return subject.isPresent()
+ && subject.getMethod().hasCode()
+ && subject
+ .streamInstructions()
+ .anyMatch(
+ instructionSubject ->
+ instructionSubject.isConstClass(classReference.getTypeName()));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("contains constclass");
+ }
+
+ @Override
+ public void describeMismatchSafely(MethodSubject subject, Description description) {
+ description.appendText("method did not");
+ }
+ };
+ }
+
public static Matcher<MethodSubject> instantiatesClass(Class<?> clazz) {
return instantiatesClass(clazz.getTypeName());
}
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 867d312..fe921e2 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -15,7 +15,7 @@
import utils
-R8_DEV_BRANCH = '8.0'
+R8_DEV_BRANCH = '8.1'
R8_VERSION_FILE = os.path.join(
'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
@@ -193,7 +193,8 @@
# Don't upload if requested not to, or if changes are not committed due
# to --use-existing-work-branch
if not options.no_upload and not options.use_existing_work_branch:
- process = subprocess.Popen(['repo', 'upload', '.', '--verify'],
+ # TODO(b/260680525): Consider removing "-o banned-words~skip" if b/260680525 is resolved.
+ process = subprocess.Popen(['repo', 'upload', '.', '--verify', '-o' 'banned-words~skip'],
stdin=subprocess.PIPE)
return process.communicate(input=b'y\n')[0]